macOS · Swift/AppKit  ·  Linux · Rust/GTK

One tiny pet.
Every agent session.

session-pet is a pixel-art desktop companion that watches all of your Claude Code and Codex sessions at once — it bounces while agents work, dings the moment one needs you, and levels up as you ship.

Mochi · cat
Ember · dragon
Inky · octopus

#What it does

A frameless, truly transparent, always-on-top window — clicks only land on the pet's opaque pixels. Native on both platforms, zero dependencies: Swift/AppKit on macOS, Rust/GTK on Linux, sharing one pet and one XP pool.

Watches every session

One pet tails all Claude Code (~/.claude/projects/*.jsonl) and Codex (~/.codex/sessions/**/rollout-*.jsonl) transcripts at once, normalizing both providers to the same phases.

Needs-input detection

Dings when an agent literally asks you something — AskUserQuestion/ExitPlanMode for Claude, request_user_input for Codex — and catches Claude Code permission prompts via an optional Notification hook.

Stalled sessions

A session that goes silent mid-turn (hung tool, unhooked permission prompt, crash) is flagged stalled in amber instead of quietly pretending to work — and still dings if a late turn-end arrives.

Sounds you can trust

A double ping when an agent needs you, a quiet chime when a turn finishes. Debounced, one ding per session, mutable from the panel. The false-positive budget is zero: a wrong ding is release-blocking.

Jump back to the terminal

Click a session card and the terminal running it comes forward — the exact tab in iTerm2, Terminal.app and Ghostty, matched on the session's tty or working directory. No hook, no shell integration.

XP, stages & 8 species

Earns XP from your sessions; an egg hatches at 30 XP and grows hatchling → adult → legendary. Pick from cat, dragon, crab, octopus, dino, fox, alien, or turtle in the visual sprite picker.

Live session cards

Click the pet for a panel of per-session cards: project badge, phase-tinted border, current tool + command/file, context size (ctx 84k), and the agent's last message snippet.

working needs input ready stalled sleeping

#Install

One tool, one installer — macOS and Linux, native on both. Clone + build from source:

# linux build deps: sudo apt install libgtk-3-dev  (+ cargo via rustup.rs)
# macos build deps: Xcode Command Line Tools (swiftc) — macOS 13+
$ git clone https://github.com/chud-lori/session-pet.git
$ cd session-pet
$ ./install.sh --login-item   # build + run + start at every login

Or skip the clone and toolchain — downloads a prebuilt from the latest release (needs a published release; prebuilts only need python3 + the GTK3 runtime already on GNOME/KDE/XFCE desktops):

$ curl -fsSL https://raw.githubusercontent.com/chud-lori/session-pet/main/install.sh | sh

Add -s -- --login-item to start at every login. ./install.sh --uninstall removes it on either OS, leaving your pet's state untouched. Wayland/compositor notes live in the INSTALL.md.

Optional — permission-prompt alerts: to get a ding on Claude Code permission prompts, merge this fragment into ~/.claude/settings.json under "hooks" yourself (the installer never edits your settings; requires jq):

"Notification": [{"hooks": [{"type": "command", "async": true,
  "command": "jq -c . >> /path/to/session-pet/.state/events.jsonl"}]}]

#Using the pet

Click the pet → session panel (click again or anywhere outside to close) · drag to move it anywhere · right-click → menu (open panel / sound on-off / quit). In the panel: click a session card to jump to the terminal running it (and acknowledge its alert), or right-click it for details (path, context tokens, last message); settings ▸ holds the species picker, the sound toggle, and the wander toggle.

Wandering: the pet takes short strolls along your screen with an animated walk cycle — never while something needs your attention, never with the panel open, and it stays wherever it (or you) last moved it. Sprite packs can ship hand-drawn "walk" frames; packs without them get an automatic two-frame leg shuffle.

Start / stop from a shell with the bundled helper — the pet also comes back automatically at next login:

$ ./pet          # start — or bring it back after quitting
$ ./pet stop     # quit (same as right-click → Quit)
$ ./pet status   # is it running?

Sounds: a quiet Glass when a turn finishes; a louder double Ping when an agent needs your input — repeating every 45 s (max 3×) until acknowledged, so it cuts through whatever you're watching. Muted? The pet jumps excitedly on every alert and keeps a small reminder hop going (with the "!" badge) until acknowledged — motion works when sound can't.

#How it works

No SDKs, no daemons inside your agents — just careful reading of the transcript files the agents already write.

1

Tail, don't read

Every couple of seconds the pet reads only the tail of each recent transcript (last 64 KB, escalating to 512 KB when a turn spans further back), with lossy UTF-8 decoding so a stray byte never breaks parsing. Gigabyte-long sessions cost almost nothing.

2

Turn-end from the transcript, not the clock

A session is "done" only when its last event says so — stop_reason: end_turn for Claude, task_complete for Codex. A long tool run or thinking pause never counts as done, so there are no false "ready" dings.

3

Stop-hook continuation detection

A Claude end_turn is not always the end of the turn: a blocking Stop hook can continue the conversation seconds later (one real session produced 33 such intermediate end_turns). The pet spots the "Stop hook feedback" event that follows and keeps the session in working — one ding, at the real end.

4

Permission prompts via hook

Claude Code permission prompts don't appear in the transcript, so an optional Notification hook appends them to .state/events.jsonl; the pet tails that file and turns them into needs input. Sessions silent mid-turn without a hook event go stalled after a grace period.

5

One shared brain

Species, name, XP, and the sound toggle live in .state/state.json — shared with the (deprecated) Python statusline pet, so your pet keeps its identity everywhere. Sprites come from native/assets.json, the pixel-map source of truth (the ones on this page are rendered straight from it).

#FAQ

What sounds does it play, and how do I mute them?

Two macOS system sounds, played via afplay: Ping.aiff when an agent needs your input and Glass.aiff when a turn finishes. Each has its own debounce clock, so a needs-input ping is never swallowed by a just-played ready ding.

Mute from the pet's panel (Sound checkbox) or the right-click menu — the setting persists in .state/state.json.

Which environment variables does the pet understand?
  • SESSION_PET_HOME — override the home directory the pet scans for ~/.claude and ~/.codex (used by the test suite to point at fixtures).
  • SESSION_PET_LOG=1 — enable debug logging.
How do I set up the permission-prompt hook?

Merge the "Notification" hook fragment printed at the end of ./install.sh (also shown in the Install section above) into ~/.claude/settings.json under "hooks". The installer deliberately never edits your settings for you.

The hook needs jq (brew install jq). It runs async and appends one JSON line per notification to .state/events.jsonl; the pet truncates that log itself so it never grows unbounded. Without the hook everything else still works — permission prompts just surface as stalled after a grace period instead of an instant ping.

Does it work with both Claude Code and Codex?

Yes — that's the point. Both providers are parsed by per-provider tailers and normalized to the same phases, so one pet (and one panel) covers every session on the machine. Deliberately only those two: no 17-provider support, ever.

How do I uninstall it?

./install.sh --uninstall unloads and deletes the LaunchAgent (~/Library/LaunchAgents/com.session-pet.plist) and stops the running pet. The repo and your pet's state are left untouched — delete the cloned directory if you want it all gone. If you added the Notification hook, remove that fragment from ~/.claude/settings.json too.

Can the pet approve permission prompts or chat with the agent?

No, by design. The pet notifies; you act in the terminal. In-pet approve/deny, chat-with-agent, and menu-bar rewrites are explicit non-goals — it stays a small floating pet that never lies about session state.