---
title: "A desktop pet that watches Claude Code"
slug: claude-pet
date: 2026-06-22
tags: ["swift", "macos", "claude-code", "build"]
summary: "A floating macOS pet that mirrors your Claude Code sessions in real time. Runs entirely on hooks, renders Codex sprites, and is one tiny Swift binary that installs and updates itself."
canonical: https://abrahamgonzalez.dev/blog/claude-pet
---

# A desktop pet that watches Claude Code

I run a few Claude Code sessions at once and I kept losing track of them. One is mid-edit, one is waiting on me to approve something, one finished ten minutes ago and I never noticed. So I built a little pet that sits in the corner of my screen and tells me, at a glance, what each session is up to.

It is called Claude Pet. A small animated creature floats above every window and across all Spaces, and its animation changes based on what Claude Code is actually doing. I glance up, see it waiting, and I know a session needs me. See it running, I leave it alone.

## Hooks, not polling

The whole thing runs on Claude Code hooks. Every hook fires a tiny command that writes one JSON file per session to `~/.claude-pet/sessions/`, and the app watches that folder and animates. No polling of Claude, no network call anywhere. SessionStart makes it wave. UserPromptSubmit and PreToolUse make it run. A notification or a permission request makes it wait. Stop makes it jump and then settle into a review pose. StopFailure makes it look defeated.

That is the core of it: every state a session can be in maps to a real animation, driven by a real event, with no guessing about what Claude is doing.

## Codex sprites, for free

I did not expect to care about this part as much as I ended up caring. There is already an ecosystem of pet sprites built for Codex, and they ship as a specific atlas: an 8 by 9 grid of cells, nine animation states, one WebP file. Instead of inventing my own format I decided to render that one exactly. So any pet from codex-pets.net drops straight in. Download a `spritesheet.webp`, load it, done. I get a whole library of art I never had to draw, and there is a built-in mascot for anyone who does not want to bother.

## Calm at rest

My first version showed labels and panels all the time and it drove me up the wall. A status widget that is always shouting is just noise. So now at rest it is only the pet. Hover it and a thought bubble drifts up with what is going on in plain language: `editing main.swift`, `answer Claude`, `all done, your turn`. Move away and it fades back to nothing.

The bubble's border doubles as a context gauge. It fills from green to amber to red as the session's context window fills up, so I get a heads-up before Claude auto-compacts. It is exact when the app supplies the status line, and it falls back to estimating from token counts when I already have my own.

## One tiny binary

It is a single Swift binary, AppKit only, no runtime dependencies. It runs in two modes. Hand it an argument like `--state waiting` and it does its job and exits, which is what the hooks call. Give it no arguments and it is the GUI overlay, held to one instance with a pidfile. The app wires its own hooks into `settings.json` without clobbering anything you already have, updates itself from GitHub Releases, and uninstalls itself, so there are no installer scripts to babysit.

I built most of this last week. It is small and a little silly and I use it every day now.

The code is on GitHub at [theabecaster/claude-pet](https://github.com/theabecaster/claude-pet) if you want to run it or load your own pet.
