Virtual desktop pet cats for Linux (GNOME/Wayland) -- pixel art cats that roam your screen and chat with you via AI.
Port of CATAI (macOS/Swift) to Linux.
- Desktop companion -- Cats roam freely across your screen with pixel-perfect animations
- Click-through -- Cats float above all windows, clicks pass through to apps below
- 6 unique characters -- Pre-colored 80×80 sprites from the catset collection, each with a distinct look and personality
- AI chat -- Click a cat to open a pixel-art chat bubble, powered by Claude or Ollama
- Voice chat 🎤 (optional) -- Hold the mic button or simply hold Space to talk to your cats. 100% local transcription via faster-whisper, GPU-accelerated if you have CUDA
- Wake word 👂 (optional) -- Each cat answers to its own renameable first name. Say "Mandarine" or "Tabby" out loud and the matching cat opens its chat bubble + starts listening. Powered by Vosk (offline, ~41 MB FR model, Apache-2.0)
- Voice commands 🗣️ -- Chain a verb after a cat's name to control it directly: "Mandarine sleep", "Tabby come" (to cursor), "Ombre tell" (tell a story), "Noisette dance" (mini-disco), plus jump and roll. No chat bubble, just instant action.
- Rich animations -- 23 animation states including running, dashing, sleeping, grooming, climbing, wall grab, ledge climbing, dying & resurrection, and more
- Animation sequences -- Multi-step scripted behaviors: wall adventures, ledge climbing, dash crashes, dramatic deaths with resurrection
- Visual overlays -- Floating ZzZ, hearts, speed lines, hurt stars, skulls, sparkles above cats during animations
- Random meows -- Cats spontaneously say "Miaou~", "Prrr...", "Mrrp!" in cute speech bubbles
- Drag & drop -- Drag cats anywhere on your screen
- Cat encounters -- When two cats cross paths, they stop and have a short AI-generated conversation
- Love encounters & kittens -- Sometimes a cat falls in love; if the other reciprocates, a kitten is born with the parent's color (fade-in + sparkles). Kittens can't chat yet — they meow with baby emojis 🐭 🍼 ♥ 🧸
- 22 hidden easter eggs 🥚 -- type
easter eggin a chat bubble for the full menu, or trigger any one directly with a magic phrase:nyan,rain,hug,42,matrix,thanos,boss fight,disco,stampede,meow party,don't panic... and many more. Even Nyan Cat with a proper 6-frame animated sprite and wavy tiled rainbow trail! - Multilingual -- French, English, Spanish
- Persistent -- Cats remember their conversations between sessions
Each character is a pre-colored sprite with a unique personality:
| Sprite | Name (default) | Personality | Specialty |
|---|---|---|---|
| 🟠 Orange | Mandarine | Mischievous and playful | Pranks and movement |
| 🟤 Tabby | Tabby | Curious and adventurous | Exploring every corner |
| ⬛ Dark | Ombre | Mysterious and silent | Rare but profound words |
| 🟫 Brown | Noisette | Gentle and comforting | Cuddles and good vibes |
| 🩶 Grey | Brume | Wise and philosophical | Deep thoughts about life |
| 🖤 Black | Minuit | Elegant and nocturnal | Mysterious stories |
Names and languages adapt to your selected language (FR / EN / ES).
CATAI-Linux supports two AI backends for cat conversations:
| Backend | Setup | Speed | Cost |
|---|---|---|---|
| Claude (recommended) | Auto-detected if Claude Code is installed | ~1-2s | Included with Claude subscription |
| Ollama | ./setup-ollama.sh |
~2-5s | Free (local) |
Claude is auto-detected from Claude Code's credentials (~/.claude/.credentials.json) or the ANTHROPIC_API_KEY environment variable. If neither is available, CATAI falls back to Ollama.
- Linux with GNOME, KDE, or any X11/XWayland desktop
- Python 3.10+
# System dependencies (GTK4 + Cairo bindings, not available via pip)
# Fedora:
sudo dnf install python3-gobject
# Ubuntu/Debian:
sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-4.0
# Install from PyPI
pip install catai-linux
# Optional: adds ~100 MB of extra deps (faster-whisper + CTranslate2)
# needed for the push-to-talk voice chat feature. Only install this if
# you actually want to talk to your cats.
pip install catai-linux[voice]cataiRight-click any cat to access Settings:
- Language -- French / English / Spanish
- Characters -- Click a sprite to add it, click again to select it (shows name + personality), click × to remove
- Name -- Rename each cat directly in the settings panel
- Size -- Scale slider
- Model -- Choose between Claude and Ollama models
- Autostart -- Launch at login
- Cat encounters -- Enable/disable random cat-to-cat conversations
- Voice chat -- Enable push-to-talk mic button + hold-Space recording (requires
pip install catai-linux[voice]) - Voice model -- Pick the Whisper model (tiny → large-v3), each annotated with size and recommended device
- Single fullscreen transparent canvas with Cairo rendering
- XShape / GDK input region passthrough -- clicks go through to apps below
- 80×80 pre-colored PNG sprites (catset by seethingswarm, upscaled 2× with nearest-neighbor)
- 23 animation states with multi-step sequences (wall adventure, ledge climbing, dash crash, dramatic death)
- Generalized pixel-accurate offset compensation for seamless transitions between any animation
- Visual overlays via PangoCairo: ZzZ, hearts, speed lines, hurt stars, skulls, sparkles, anger marks
- Claude API or Ollama for streaming AI chat
- Lazy loading + disk cache for instant startup
- Config persisted in
~/.config/catai/
make lint # Run ruff linter
make fix # Auto-fix lint issues
make test # Run headless unit tests (59 assertions, no GTK display needed)
make e2e # Run the full E2E test suite (48 assertions via Unix socket)
make run # Launch the app
make build # Build wheel + sdistmake e2e runs locally against a real GDK display + a real AI backend. In
CI, the same suite runs inside the e2e-tests job of
.github/workflows/test.yml via Xvfb + metacity + a MockChat backend
activated by CATAI_MOCK_CHAT=1, so every PR gets the 48 assertions
validated against a fresh Ubuntu runner before merge.
Sprite conversion (catset spritesheets → CATAI character directories):
python3 scripts/catset_to_catai.py catset_assets/catset_spritesheets catai_linux/Regenerate README assets (screenshots + demo GIF, no screen capture required):
python3 tools/render_screenshots.py # → screenshot1..3.png
python3 tools/render_demo_gif.py # → demo.gif.
├── catai_linux/ # Python package
│ ├── __main__.py # Entry point (python -m catai_linux)
│ ├── app.py # Main application + CatAIApp + CatInstance
│ ├── chat_backend.py # Claude + Ollama streaming backends
│ ├── voice.py # Push-to-talk recording + Whisper transcription
│ ├── drawing.py # Cairo/Pango helpers (bubbles, overlays, CSS)
│ ├── x11_helpers.py # Low-level Xlib/XShape via ctypes
│ ├── l10n.py # Localization strings (fr/en/es)
│ ├── cat_orange/ # Sprite assets — orange cat (80×80 PNG)
│ ├── cat01..05/ # Sprite assets — tabby/dark/brown/grey/black
│ └── kitten*/ # Kitten sprites (inherited from cat parent)
├── scripts/
│ ├── catset_to_catai.py # Catset spritesheet conversion
│ ├── kittens_to_catai.py # Kitten spritesheet conversion
│ └── sd_postprocess.py # Stable Diffusion output post-processing
├── tools/
│ ├── sprite_preview.py # Visual preview of all sprites + overlays
│ ├── render_screenshots.py # Generate README screenshots
│ ├── render_demo_gif.py # Generate demo.gif
│ └── render_love_demo.py # Generate love_demo.gif
├── tests/
│ └── e2e_test.py # E2E test suite (26 tests via Unix socket)
├── pyproject.toml # Package config + linter config
├── Makefile # make run / lint / e2e / build
└── .github/workflows/ # CI: lint + PyPI publish
- Original macOS version: wil-pe/CATAI
- Catset sprites: seethingswarm/catset (CC0)
MIT
- Feeding mechanic: right-click any cat → "🍽 Nourrir" → the cat eats from a pixel-art food bowl drawn at its feet. Drops hunger by 60, bumps happiness by 5, with a 30s per-cat cooldown
- Hungry signal: when hunger > 70, cats show 🐟 as a random meow bubble so you notice they'd like to be fed
- "Feed everyone" easter egg: triggered from the easter egg menu or by typing any of
feed,feed all,food,dinner,nourrir,mangeretc. in the chat — feeds all cats at once, bypassing cooldown - Nocturnal behavior: Ombre and Minuit (whose personalities explicitly evoke the night) now drift into
sleeping_ballmuch more often during the day, while the four diurnal cats (Tabby, Mandarine, Noisette, Brume) sleep spontaneously only at night. Observed live: Minuit at 25.6% sleep during a 90s daytime monitor vs 0% for the diurnal cats - Fixed right-click context menu — after the bump from 2 rows to 3 rows (Feed/Settings/Quit), the left-click hit-test was still checking the old 50px layout, so "Nourrir" opened Settings, "Réglages" quit, and "Quitter" did nothing. Also fixed the cairo input region mask that was still 50px tall, causing the "Quitter" row to silently reject all mouse events
- Kittens now share the adult distribution instead of being walking-dominated: walking 34.5% (was 57%), idle 8.7% (was 15.6%), with 11 distinct animations visible at >3% (grooming, love, wallgrab, eating, rolling, jumping, dashing, flat, angry, chasing_mouse, …)
- Root cause: kittens have 17 anims vs adults' 44, so the 58% "new anims" probability bucket picked animations kittens don't have, falling through to an instant IDLE → walking dominated the time-share. New kitten-only branch picks from a pool of mini-actions the kitten actually owns
- Kitten pool animations loop 3× (like adult decorative anims) so each dwells ~3s instead of 1s, preventing idle churn between short anims
- Added a watchdog: any cat stuck in the same non-IDLE / non-WALKING / non-SLEEPING_BALL state for >60s is force-reset to IDLE. Catches future cases of stuck
draggingorin_encounterflags before they freeze a cat for the session
- Fixed BANDAGED loop bug: cats no longer get stuck bandaged when happiness decays. Threshold tightened (<10, was <20) and triggers only 20% of the time
- Rebuilt behavior_tick probability buckets: re-added LOVE, ROLLING, SURPRISED, JUMPING states that were unreachable (dead code), 12 mini-actions at 2% each
- Simplified mood biasing to only rest/bored (removed grumpy/affectionate biases that pointed at stale bucket positions)
- Happiness passive decay floor raised from 15 to 30 (above grumpy threshold) so idle cats don't drift into permanent grumpiness
- Mean-reverting vertical movement: cats in the bottom half are biased upward when walking, prevents the "bottom-sink" pileup
- Walking destinations constrained to ±250px (was full screen width), walks now last 5-8s instead of 26s
- Hard 25s cap on walking to prevent stuck cats when destination becomes unreachable
- Walking-in-puddle animation plays once (was 3× loop which caused the cat to visually teleport back to the puddle start)
- Walking-in-puddle regenerated for all 6 cats: puddle stays fixed at canvas center while cat walks through it
- Wall/climb behaviors only trigger when not near screen edges, with forced walk-to-center escape when at edges
- ASCII cat added to CI workflow success output
- All 17 new animations properly regenerated per-cat using each cat's own source sprites (was orange-only)
- Quake-style drop-down console toggled with ² key (works from any app via XGrabKey, fallback right-click menu)
catai-shellCLI tool for scripting cat actions over the Unix API socket- Extended API socket with 6 new commands (state, mood, say, walk, animate, list)
- 17 new sprite animations across 3 batches: chasing-butterfly, playing-ball, dancing, stretching, yawning, pouncing, sitting-with-bird, fishing, sneaking, hello-kitty, bandaged, pirouette, rolling-on-back, bothered-by-bee, bothered-by-fly, sleeping-by-fire, walking-in-puddle
- Escape key closes chat bubble
- Clean Ctrl+C exit handling
- Console I/O logging for debugging
CATAI-Linux reaches v1.0 — the codebase is now modular, tested, documented, and ready for contributions.
catai_linux/constants.py(209 lines) —CatStateenum, animations, personalities, sequences, all scalar constants extractedcatai_linux/encounters.py(391 lines) —CatEncounter+LoveEncounterclassescatai_linux/settings_window.py(755 lines) — fullSettingsWindowclass- (already in v0.9.0)
catai_linux/easter_eggs.py(1535 lines) — 30 easter eggs as a mixin
- 3 mixin attributes (
_apocalypse_queue,_apocalypse_spawning,_matrix_ticks) added toCatAIApp.__init__— were previously created dynamically viahasattr() - 3 modules added to the smoke test (
easter_eggs,config_schema,theme) getattrpattern cleaned up inchat_backend.py- Fixed
voice_modelresetting to"base"on every launch — thechoicesconstraint in the config schema was too restrictive
- Unified Makefile:
make lint,make test,make e2e,make run,make messages(compile gettext),make release,make clean - CONTRIBUTING.md — guide for adding an easter egg, a character pack, a voice command, or a translation
- 407 tests pass (0 failures)
- 12 Python modules extracted from
app.pyin total - ruff clean across the entire codebase
Logical follow-up to the v0.8.0 audit — 6 structural improvement tracks.
- 30 easter eggs + menu + drawing extracted from
app.pyintocatai_linux/easter_eggs.py(1535 lines). Mixin pattern:CatAIApp(EasterEggMixin, Gtk.Application).app.pygoes from 6797 to 5357 lines (-1440). Zero logic changes.
from __future__ import annotationsonapp.pyCatInstance.__init__and 8 public methods annotated- Key
CatAIApp.__init__attributes annotated - Module-level functions (
load_config,save_config,pil_to_surface,load_sprite, etc.) annotated
- New module
catai_linux/config_schema.py(~140 lines) CONFIG_SCHEMAdict with type/default/min/max/choices for all 18 config keysvalidate_config()fills defaults, clamps numerics, validates choices, logs unknown keys, never raises- Wired into
do_activate:cfg = validate_config(load_config())
- 23 translation keys (FR/EN/ES) migrated from inline dict to
real gettext
.po+.mocatalogs compiled incatai_linux/locales/ L10nbackward-compat preserved via a metaclass:L10n.lang = "en"triggers gettext reload.L10n.s("key")andL10n.random_meow()unchanged API-side..mofiles included in the wheel viapyproject.toml
@functools.lru_cache(maxsize=512)onload_sprite(PIL Image)_surface_cachedict +pil_to_surface_cachedwrapper (cairo surfaces cached by(path, w, h))- When 6 cats share the same catset, the sprite is loaded and converted only once instead of 6 times. Cleared at shutdown.
- 48 new assertions: config schema (15), easter eggs data (10), CatInstance init (11), pil_to_surface swap (7), sprite cache (5)
- Total 401 passed / 0 failed (up from 353)
The current daemon threads + GLib.idle_add are idiomatic in GTK4.
Switching to asyncio would require a gbulb/asyncio-glib bridge
with uncertain maintenance. Disproportionate risk/effort.
Full codebase audit after the v0.7.x series. Zero new features, 100% quality.
_CatsetMarker+_CATSET_COLOR_DEF(remnant from v0.3.0 color-tinting)self.color_def+color_def_objparameter inCatInstance.__init___check_deps()empty method + call indo_activateactive_ids(legacycolor_idset) inSettingsWindow._buildself._entry_window(attribute never read)import reinline insend_chat()— moved to top-levelfrom math import loginline inmemory.py— moved toimport mathtop-level
for/elsetrap in_apply_sequence_offset_compensation— theelseclause ran when the loop didn't break, not when_sequencewasNone. Replaced with an explicitif/elsewith bound-safemin().- Double
now = time.monotonic()in_check_encounters— the second assignment needlessly overwrote the first. - Dynamic attributes on
CatInstance—_state_tick,_die_threshold,_die_resurrect,_sleep_tick,_petting_active,_hidden,_boss_scale,_beam_ticks,_rm_rf_active,_nyan_activeare now all explicitly initialized in__init__, not via scatteredgetattr()calls across 4000 lines.
- Test socket moved from
/tmp/catai_test.sockto$XDG_RUNTIME_DIR/catai_test.sock+os.chmod(path, 0o600)after bind. Previously, any user on the system could send commands (force_state, quit, type_chat, etc.). - Socket DoS timeout —
conn.settimeout(5.0)added on both sockets (API + test) aftersetblocking(True). A malicious client connecting without sending no longer blocks the GTK main loop indefinitely. - Visible warning log in the auto-updater before using
--break-system-packages(PEP 668 fallback). Conscious design choice, but now the user sees it in the logs. - Explicit comment in
chat_backend.pyabout the choice not to refuse reading credentials with overly open permissions (trade-off: breaking the app vs alerting the user).
pil_to_surface— RGBA-to-BGRA swap now usesPIL.Image.split()- native
merge()instead of a per-pixel Python loop. ~50x faster on an 80x80 sprite.
- native
_sprite_floor_y+_sprite_center_x— now usePIL.Image.getbbox()on the alpha channel instead of a per-pixel scan. From O(w*h) Python to a single C-level call.- Metrics —
track()now accumulates in memory and flushes every 30 s (+ at shutdown) instead of load()+save() on every call. Saves 2-3 disk writes per second during active chat. memory.py—from math import logmoved out of the hot-path loop to a top-levelimport math.
- Rename
cat01tocat_01(naming convention) — requires a user data migration. Deferred. - Extract
SettingsWindowandCatEncounterinto separate modules —app.pyis 6700+ lines but structural refactoring risks cascading regressions. Deferred.
Surprise bug: when the Claude OAuth token was fully expired (not
just the access token but also the refresh token), our
_refresh_claude_token helper ran claude -p ok which fell back to
the interactive auth flow and opened a browser window to the Claude
marketing/login page without warning, right in the middle of any
background action (encounter, reaction pool, drift, memory extraction,
etc.).
Fix: claude -p ok now runs in a headless-only environment by
stripping DISPLAY, WAYLAND_DISPLAY, and BROWSER, plus forcing
BROWSER=/bin/false. The CLI can no longer pop a browser. If the
refresh truly fails, the chat cleanly raises err_auth and the bubble
shows a polite message — the user can re-auth manually with
claude -p ok from a real terminal whenever they want.
5 new unit tests that mock subprocess.run and assert that the
passed environment strips everything it should. Total 353 passed / 0 failed.
Wake word extension: instead of opening a chat every time you call a cat by name, you can now chain a verb to control it directly, without a chat bubble or push-to-talk listening. Six French verbs to start with.
| Phrase | Action |
|---|---|
| "Mandarine" | Previous behavior: opens chat + starts a 6 s PTT |
| "Mandarine dors" | Cat enters SLEEPING_BALL immediately |
| "Mandarine viens" | Cat walks to the mouse cursor |
| "Mandarine raconte" | Opens chat and injects a "tell me a story" prompt to the AI |
| "Mandarine danse" | Mini-disco loop on that cat (LOVE/ROLLING/GROOMING alternation, 5 sec) |
| "Mandarine saute" | JUMPING animation |
| "Mandarine roule" | ROLLING animation |
Technical details:
- Extended Vosk grammar — the 6 verbs are added to the recognizer's closed grammar alongside the cat names. Vosk composes freely, so "Mandarine dors" is transcribed as a single result.
- 2-token look-ahead parser — after matching a cat name, the parser looks for a verb in the next 2 tokens. Allows the user to hesitate ("uh Mandarine, dors") while avoiding false positives on verbs that are too far away.
viensuses XQueryPointer — new Xlib ctypes helperget_mouse_position()that reads the absolute cursor position without forking. Falls back to a random point on monitor 0 if the query fails.raconteis localized — the prompt sent to the LLM is translated to FR/EN/ES based onL10n.lang, so the response language follows your config.dansescoped to a single cat — reuses the celebratory state list from theeg_discoeaster egg but only puts the called cat into dance mode; the others carry on. Auto-cleanup after 5 s.- Backward compat — the old
on_wake(cat_id)signature is still supported via aTypeErrorfallback in_fire. All existing consumers keep working. - 23 new unit tests: COMMAND_VERBS, parsing, look-ahead, callback dispatch, legacy compat, get_mouse_position safety. Total 348 passed / 0 failed.
No new dependencies — Vosk was already in [voice].
Following the discussion around native Wayland porting (#7): as long
as GNOME Mutter doesn't support wlr-layer-shell (open issue since
2019, no sign of movement), CATAI must stay on Xwayland on GNOME
because XShape has no equivalent without layer-shell. So we did an
intermediate cleanup to improve code quality without changing the
architecture.
xprintidlesubprocess removed — idle detection now goes throughorg.gnome.Mutter.IdleMonitorD-Bus viaGio.DBusProxy(native PyGObject, zero new deps). Subprocessxprintidlefallback on non-GNOME setups, fallback0otherwise. Call cost: ~0.1 ms vs ~5-15 ms for a fork().xpropsubprocess for fullscreen removed —_is_any_fullscreen()now uses 2 directXGetWindowPropertyctypes calls (_NET_ACTIVE_WINDOWthen_NET_WM_STATE). ~50 us total vs ~30 ms for the previous 2 fork() calls. Fullscreen polling runs at 0.67 Hz so the cumulative saving is ~20 ms/sec.xdotool getwindowgeometryremoved — GNOME top bar Y offset detection now uses directXTranslateCoordinatesctypes call. One-shot at boot, but one fewer fork().- Dead subprocess fallbacks removed —
_run_x11()and thexdotool windowmove/wmctrl/xprop -setfallbacks were unreachable in practice (libX11 is always available). 30 fewer lines of plumbing. subprocessmodule removed entirely fromapp.py— one fewer file to scan for understanding external dependencies.- 3 runtime deps removed:
xdotool,xprop,xprintidleare never called anymore. You can uninstall these packages without breaking anything. Still required formake e2e(the test harness, not the app itself).
No visible functional changes. CPU savings: ~6 fork() calls removed per second, sub-1% load. More importantly: cleaner, more testable code, future-proof against the gradual deprecation of X11 sessions on GNOME.
- Fix — Claude/Ollama responses were sometimes truncated with "..."
in the middle of a sentence because the chat bubble was capped at 8
lines while
max_tokens=256produces up to ~14 lines at the current width. Cap raised to 16 lines (drawing.py+app.pyin lockstep). No normal response should get cut off anymore.
Issue #4 Tier 2: calling a cat by its name opens its chat bubble and starts automatic push-to-talk listening. No need to touch the keyboard or mouse.
- Renameable wake word on the fly — each cat responds to its own
name (
Mandarine,Tabby,Ombre, ...). Rename a cat from Settings and the grammar is rebuilt immediately, the old name forgotten, the new one active. No retraining needed. - Vosk backend (Apache-2.0, 100% offline). FR small model (~41 MB)
downloaded in the background on first launch, cached in
~/.cache/catai/vosk/. Idle ~3-8% CPU. - Coexistence with existing push-to-talk — the wake word pipeline
automatically releases
autoaudiosrcbefore each Whisper recording, then reclaims it afterward. - Opt-in — disabled by default, checkbox in the Voice section of the Settings panel. A second checkbox controls the confirmation meow. Vosk not installed? No problem, the module silently no-ops and the rest of voice keeps working.
- New optional dependency in the
[voice]extra:vosk>=0.3.45.wake_words_triggeredmetrics (per cat) if local stats are enabled.
Two big themes: cats now remember you across sessions and the ecosystem opens up for community character packs + shell-script integration. Five PRs landed (#26-#30) closing issues #5 and #9.
- Long-term memory (#29) — each cat keeps a per-cat sqlite store
of "memorable facts" at
~/.config/catai/memory.db. Every 20 chat exchanges, the AI extracts 0-3 facts about you from the recent conversation. On every new message, the keyword-overlap retriever finds the top 3 facts that share words with your input and injects them into the system prompt as "things you remember about this user". No embeddings, no heavy ML deps — pure stdlib + sqlite. - Inter-cat gossip (#30) — when two cats encounter each other on
the canvas (the existing love encounter trigger), they each pick
one random fact from their memory pile and add it to the other
cat's memory, prefixed with
<cat name> told me:. Recipients later surface that fact as second-hand knowledge. Effect: chat with Mandarine for an hour → she remembers your name → she meets Ombre → Ombre also knows.
- Plugin API for character packs (#28) — drop a folder under
~/.local/share/catai/characters/<char_id>/withmetadata.json,personality.json, and sprite directories, and CATAI auto-loads it as a new playable cat at startup. Strict validation, silent skip on errors. Lets contributors ship custom catset characters without forking. New modulecatai_linux/character_packs.py. - Scriptable hooks via Unix socket (#27) — opt-in via
api_enabledin config.json. Opens$XDG_RUNTIME_DIR/catai.sock(mode 0600, same-user only) with curated commands:status,list_cats,list_eggs,meow <idx> [text],egg <key>,notify,help. Lets shell scripts make cats react to external events:make test && echo 'egg meow_party' | nc -U $XDG_RUNTIME_DIR/catai.sock
- Local metrics dashboard (#26) — opt-in privacy-first stats
tracker at
~/.config/catai/stats.json. Tracks chats sent, voice recordings, eggs triggered, love encounters, kittens born, pet sessions, per-cat counters. Settings panel shows live summary with top petted cats and top eggs, plus a 'Reset stats' button. Zero telemetry, never transmitted anywhere.
CATAI now updates itself. Once installed via pip, every launch
silently checks GitHub for a new release, runs pip install --upgrade
in the background, and notifies you with a meow bubble that the new
version is ready for the next launch. (#24, #25)
- Auto-update on launch — new
catai_linux/updater.pypollsapi.github.com/repos/Gheop/CATAI-Linux/releases/latest(cached 1 h), compares to the installed version viaimportlib.metadata, and runspython -m pip install --user --upgrade catai-linux[voice]in a subprocess if a non-prerelease update is available. PEP 668 systems get an automatic--break-system-packagesretry. - 3 modes in Settings → Updates dropdown:
Auto-install on launch(default) — check + install + notifyNotify only— check + meow bubble, no installOff— no network call
- 'Check now' button in settings bypasses the 1 h cache.
- Privacy + safety — one anonymous GET to api.github.com per
hour, no telemetry,
pip --userscoped (never touches the system Python), all failure modes silent so a network blip can't break the running app. New code activates only on the NEXT launch.
Closes the loop with v0.4.0 voice input: cats now speak their chat responses out loud. Hybrid pipeline mixes real CC0 cat sound samples (meow, purr, mrrp, hiss) with Piper TTS for the text portions, so 'purr Hello my friend!' plays a real purr sample then a synthesized voice saying 'Hello my friend', not a human pronouncing 'purr' phonetically. (#23)
- TTS hybrid pipeline — new
catai_linux/tts.pysplits each AI response into alternating cat-sound + text chunks. Cat tokens play CC0 WAV samples bundled incatai_linux/sounds/(165 KB). Text chunks go through Piper TTS (fr_FR-upmc-medium, ~74 MB, downloaded to~/.cache/catai/piper/on first use). - 6 distinct cat voices —
fr_FR-upmc-mediumis multi-speaker (jessica, pierre); each catset character gets its own speaker_id × length_scale combo (Mandarine perky, Ombre slow grave, Brume sage, etc). - Speaker icon in chat bubble — 🔊 / 🔇 toggle in the top-right of every chat bubble. Click to mute that specific cat. Clicking while a cat is mid-sentence kills playback immediately.
- Sound effects toggle — Settings → 'Voice output' has a sub checkbox to play TTS text only (no cat samples) for users who find the interjections distracting.
- Pixel-art mic / speaker / sablier icons — bundled hand-drawn
PNGs in
catai_linux/icons/matching the cream/brown bubble palette. - Seasonal overlay first-launch-only — the 30 s season announce
(snow, petals, pumpkins, etc) now only fires on the first launch
per day, tracked via
~/.config/catai/seasonal_last_shown. No more falling petals every time you restart CATAI. - Smarter prompt — system prompt now requires AT LEAST one full
real sentence per response and forbids
*stage directions*and emoji, so the TTS never reads stage directions phonetically. - Robustness fixes — every TTS playback runs in an isolated
gst-launch-1.0subprocess to avoid in-process GStreamer state contamination, and pressing the mic button stops any in-flight TTS so the recording isn't echoed back. Chat bubble tail flips to the top edge when the cat is near the screen top, and the text wraps around the speaker icon instead of running under it.
Major interactivity + visual update. Five self-contained features landed together — petting, moods, drift, theme sync, seasonal overlays, 3 more easter eggs, and multi-monitor awareness.
- Petting + invisible mood — long-press a cat to pet it. Every cat
tracks four hidden stats (happiness, energy, bored, hunger) that drift
over time and bias its emergent behavior. A grumpy cat lounges. A
bored one bounces around. Persisted in
~/.config/catai/mood_*.json. - User activity awareness — cats detect when you go AFK (> 10 min idle) and curl up to sleep until you return. They also dim activity late at night.
- D-Bus notification reactions — when a desktop notification arrives, the nearest cat reacts with a curious meow. Reaction lines are AI-generated per cat so each one sounds like themselves.
- Dark mode sync — bubbles + menus now follow your GNOME dark/light preference automatically (polled every 30 s). Opt in bit: no config needed. (#18)
- Seasonal overlays — date-aware particles drift behind the cats:
❄ snowflakes in winter, 🌼 cherry petals in spring, 🍂 autumn leaves,
🎃 Halloween pumpkins, 🎄 + heavier snow for Christmas, ♥ Valentine's,
✨ NYE firework bursts. The overlay announces the current season for
30 s on launch then fades out to leave the canvas clean. Opt out via
"seasonal": falseor"seasonal_duration_sec": 0for permanent display. (#19) - 30 easter eggs — added 🎮 Konami code (cheat-mode cascade
SURPRISED → LOVE → ROLLING + mood refresh), ☕ coffee rush (2× cat
speed for 15 s), and 🧘 zen mode (all cats freeze in perfect
meditation for 10 s). Magic phrases:
konami,up up down down,cheat code,coffee,espresso,caffeine,zen,meditate,calm. Existing 27 eggs still there. (#20) - Multi-monitor intelligence — cats now spawn distributed across
every attached screen instead of stacked on monitor 0. New
monitorssocket command inspects layout and detects dead-zones between mismatched displays. (#21) - Personality drift — every 10 chat messages the cat silently asks
the AI backend to reflect on the conversation and propose ONE new
"quirk" it picked up from you. Quirks accumulate (max 5, oldest
falls off) in
~/.config/catai/personality_*.jsonand flavor every future chat's system prompt. No embeddings, no RAG — pure prompt engineering. Opt out via"personality_drift": false. (#22)
Internal overhaul — no user-facing feature changes. If v0.4.0 works for you, v0.5.0 works the same, just lighter and cleaner under the hood.
- Wheel size: 2.5 MB → 1.6 MB (−36%) — removed the legacy
cute_orange_cat/sprite directory (2.1 MB of 68×68 sprites that were shipped in every release since v0.1.0 but have not been loaded at runtime since the v0.3.0 catset migration). Removed the obsoleteMANIFEST.inthat was keeping them in the distribution. - Modular architecture:
catai_linux/app.pysplit from 6044 lines into 6 focused modules. Smaller files, easier to navigate, easier to test, lazy imports for optional features:app.py(4200 lines) —CatAIApp, render loop, mainchat_backend.py(310 lines) —ChatBackend,ClaudeChat,OllamaChat,create_chat, Claude OAuth helpers, Ollama probingvoice.py(220 lines) —VoiceRecorder, Whisper model metadata, HuggingFace cache detectiondrawing.py(480 lines) — CSS theme + all Cairo/Pango drawing helpers (speech bubbles, overlays, context menu, sparkles)x11_helpers.py(310 lines) — low-level Xlib/XShape via ctypesl10n.py(44 lines) — localization strings + random meow pool
- Dead code purge: −800 lines of unused classes, helpers, and
unreachable legacy branches:
ChatBubbleControllerclass (150 lines) — old Gtk.Window-based bubble, replaced by the Cairo canvas rendering in v0.3.x- Legacy color-tinting system (
CatColorDefclass,CAT_COLORStable with 6 pre-baked personalities,tint_sprite,rgb_to_hsb/hsb_to_rgb,load_and_tintwith disk cache) - 6 unreachable
CatStateenum members (DRINKING,PLAYING_BALL,BUTTERFLY,SCRATCHING_TREE,PEEING,POOPING) plus their dispatch branches and all 7 drawing helpers that existed only to render their props (butterfly, tree with bark+foliage+scratches, pee drops, poop drops with flies, yarn ball) CatAIApp.add_cat/remove_cat/rename_cat— legacy color-based cat management, superseded byadd_catset_charetc.SettingsWindow._on_bubble_*/_on_name_changed— handlers for the old color-bubble UI, never connected to any widgetdraw_pixel_border,_pango_text_width,_load_anims_bg,_get_preview,_get_anim_frames— helpers with zero callers
- Perf: cached Pango layout across render ticks in
_position_chat_entry()(was re-allocating acairo.ImageSurface, context, layout, and font description every frame when a chat bubble was visible — ~0.5–1 ms/frame saved). Dirty-key cache for_update_input_regions()skips the expensivecairo.Regionrebuild on frames where no cat has moved (the common idle case — ~0.5 ms saved per skipped frame on an 8 FPS loop). - Code quality:
_handle_test_cmd()(197 lines, 19 if/elif branches) refactored into 21 small_cmd_*handlers dispatched via a dict — each command is now an independently-readable 5–10 line method. Added_get_cat_at_idx()helper to deduplicate index parsing. - Type hints on all public APIs of the new modules
(
ChatBackend.send,VoiceRecorder.*, drawing helpers, etc.) for IDE autocomplete and futuremypysupport. - Housekeeping:
.gitignorenow excludes raw sprite sources (catset_assets/,kittens_assets/); removed obsoletescripts/generate_sprites.py,scripts/catai_sprites.ipynb,docs/feature_kittens.md, and emptyscripts/references//scripts/sd_workflows/directories.
- Voice chat (push-to-talk) 🎤 — talk to your cats instead of typing. Hold the mic button next to the chat entry, or simply hold Space while the entry is focused and empty (just like
/voicein Claude Code). Release to auto-transcribe and send. - 100% local & private — speech-to-text runs on-device via faster-whisper. Nothing is sent to any cloud service for transcription.
- GPU auto-detection — CUDA is used automatically if available (float16), otherwise CPU int8. Tested on RTX 2050 / 5090.
- Whisper model picker in Settings — 7 models from
tiny(39 MB, CPU) tolarge-v3(1.5 GB, GPU), each annotated with size and recommended device. Changes take effect immediately, no restart needed. - Model preload at startup — if the selected Whisper model is already cached, it loads into memory in the background while the cats spawn, so the first recording is instant (no 1–3 s wait).
- Token refresh feedback — when the Claude OAuth token needs refreshing mid-conversation, the chat bubble now shows an animated braille spinner with "Refreshing Claude token..." instead of a silent stall.
- Instant click on startup — fixed a 4–5 s freeze where clicking on a cat did nothing during the first seconds. All heavy per-cat setup (sprite loading, animation offsets, pixel-scan floor detection, chat backend creation) now runs in background threads so the main event loop is responsive from t=0.
- Soft dependency — the voice feature is opt-in. Install with
pip install catai-linux[voice], then enable via--voiceCLI flag or Settings checkbox. The mic button only appears when enabled, and the feature degrades gracefully iffaster-whisperis not installed. - Settings window — auto-sized to fit screen height (max 900 px, min 480 px), scrollable for smaller displays.
- Easter egg menu 🥚 — type
easter eggin any chat bubble to open a clickable menu of 22 fun effects. The menu disappears as soon as you click an egg so you can actually see the effect - 22 easter eggs with direct magic phrases (type them in a chat bubble):
- 💥 Apocalypse (
apocalypse,kaboom, orDon't panic) — kittens double every second up to 1000 - 🌀 Circle 42 (
42,circle,answer) — cats form a HGttG circle - 🎉 Meow party (
meow,party) — all cats meow at once - 🏃 Stampede (
stampede,run) — everyone dashes together - 😴 Sleepy time (
sleep,zzz,bedtime) — mass nap - 🤗 Group hug (
hug,hugs) — cluster in LOVE - 🕺 Disco (
disco,dance) — cats cycle through colorful states - 🌧 Rain of cats (
rain,raining) — cats fall from the sky with gravity - 📳 Shake (
shake,earthquake) — global screen shake - 🌿 Catnip (
catnip,nip) — everyone rolls - 📈 Stonks (
stonks) — cats climb slowly upward - 🐌 / ⏩ Slow/Fast motion (
slow,fast) — 3× slower or 2× faster for 10s - 💀 Thanos snap (
snap,thanos) — half the cats fade and vanish - 🛸 Beam me up (
beam,teleport) — random cat shoots up with light beam - 🌍 Hello, World! (
hello,hi) — a cat says it - 🥪 sudo sandwich (
sudo,sandwich) — xkcd reference - 🙈 Hide & seek (
hide,hide and seek) — all cats vanish except one - 🟢 Matrix (
matrix,neo) — green digital rain overlay - 👹 Boss fight (
boss,fight) — one cat grows 2.2× into an ANGRY boss, others circle around. After 5s the boss dies in drama_queen while shrinking and the crowd reacts with SURPRISED - 👣 Follow the leader (
follow,follow me) — all cats walk toward the current focused cat - 🌈 Nyan Cat! (
nyan,nyan cat) — the classic Nyan Cat with 6-frame animation flies across the screen, trailing a tiled wavy rainbow
- 💥 Apocalypse (
- Nyan Cat asset pipeline:
catai_linux/nyan_cat.png(6-frame sprite sheet) +catai_linux/nyan_rainbow.png(clean 6-stripe tile) drawn via Cairo with nearest-neighbor filter - DASHING speed lines polished: foot streaks flush with the cat + dust particles around — much more "wind/motion" than the previous ladder effect
rainegg: cats now actually fall with gravity acceleration, then transition to LANDING when they hit the floorfollow_leader: robust fallback when there's no active chat cat- Test socket additions:
easter_menu,egg <key>,love_encounter <a> <b>for scripted demos
- Cats no longer drop off the bottom of the screen: the clamp now accounts for the GNOME top bar offset (detected via
_canvas_y_offset) — previously the canvas window extended ~32 px below the visible area, so cats atscreen_h - display_hwere rendered partially off-screen - Flush-to-bottom alignment: the clamp also factors in the sprite's own empty bottom padding (transparent rows between the cat's feet and the sprite box edge), so cats sit right against the screen edge with their feet visible instead of hovering 15–20 px above
- Per-cat
_sprite_bottom_padding: computed once at setup from the idle rotation's_sprite_floor_y, recomputed on scale change - Test socket
statusnow reports screen resolution and top bar offset for easier debugging move_cattest command routes through_clamp_to_screenso scripted tests honour the same bounds as normal behaviour
- Love encounter flow swap (ANGRY): when the reaction is angry, cat A is now the aggressor (starts in ANGRY from the beginning) and cat B becomes the victim who plays the drama_queen sequence — previously it was the opposite
- Cats face each other during encounters: new
_face_toward()helper on CatInstance flips the sprite horizontally when needed, since love/angry/flat sprites only exist as south (east-facing) frames - No more random deaths: the
drama_queensequence is no longer triggered randomly during idle — deaths now only happen contextually:- Wall slide crash at the bottom of the screen (40% chance after a significant slide)
- ANGRY reaction during a love encounter (cat B dies and revives)
- Wall slide "glass surface" feel: WALLGRAB now slides with acceleration (2 → 8 px/tick), slides up to ~360 px over ~4 s, much more natural movement vs the previous 1 px/tick constant
- Rebalanced behavior: climbing probability boosted (3% → 7%) and wall_adventure reduced (4% → 2%) to prevent cats accumulating at the bottom of the screen over time
- Screen-bounds clamp: triple defensive clamp (render_tick start + end + draw loop) with a 5px safety margin so cats can never be drawn off the bottom of the screen, regardless of animation or sequence side-effects
- Fix love_demo.gif: cat B now faces west (toward cat A) in all still scenes — previously was facing away
- Fix test socket: survives
ncat --send-only(BrokenPipeError is caught) so scripted testing works reliably - Makefile: new
make run-testtarget launches with--test-socket
- Love encounters: 40% of cat-to-cat encounters now skip the dialogue and become silent love moments. The initiator enters LOVE state with floating hearts, and the other cat reacts with ANGRY 💢 (30%), SURPRISED !!! (30%), or LOVE ♥ (40%)
- Kitten births: when both cats reciprocate, a kitten is born at the midpoint with a magical fade-in + grow + ✨ sparkles animation
- Kitten characters: 6 new 64×64 sprites (kitten_orange, kitten01..05) extracted from seethingswarm's kitten spritesheets via
scripts/kittens_to_catai.py. Kittens inherit one parent's color at birth (cat01→kitten01, cat_orange→kitten_orange, etc.) - Baby meows: kittens can't talk yet — their meows and click responses show random baby emojis (🐭 🍼 ♥ 🧸 🐣 🥛 ✨ 🎀 💨 🐱...)
- Kitten rules: ephemeral (not saved across restarts), max 6 at once, no chat dialogue, no transformation to adult, excluded from regular encounters
- Sprite viewer: 6 new characters in
sprite_viewer.html, proportionally smaller (kittens at 192px vs cats at 240px) - New tool:
tools/render_love_demo.pygenerateslove_demo.gifshowing the full love encounter flow + birth - Test socket: new
love_encounter <idx_a> <idx_b>command to manually trigger a love encounter - Fix: test socket no longer crashes on
ncat --send-only(BrokenPipeError is caught and logged);CatEncounter.cancelno longer warns when a timer has already fired - Makefile: new
make run-testtarget that launches with--test-socketfor automation
- New README assets: animated
demo.gif+ 3 static screenshots rendered directly via Cairo (no screen capture needed — bypasses Wayland entirely) - Rendering tools:
tools/render_screenshots.pyandtools/render_demo_gif.py— reproducible, deterministic, GIF palette-optimized (~230 KB for 14s @ 12fps) - Proactive Claude auth refresh: token is now refreshed BEFORE it expires (5 min buffer) instead of waiting for a 401, eliminating authentication errors during chat
- Test socket extensions: new commands
force_state,start_sequence,meow,move_cat,fake_chatfor scripted testing and automation - Fix:
cat_positionstest command was using the oldcolor_idfield that no longer exists on catset cats
- 10 new animations: dash, die, fall, hurt, land, wall climb, wall grab, ledge grab, ledge idle, ledge struggle
- Multi-step sequences: wall adventure (climb → grab → fall → land), ledge adventure, dash crash, full jump, drama queen (hurt → die → resurrect)
- Visual overlays: ZzZ (sleep), ♥ (love), !!! (surprised), ✦ (hurt), 💀 (dying), ✨ (grooming), 💢 (angry), speed lines (dash)
- Dashing: cats sprint across the screen at 3× walk speed with speed lines behind them
- Dying & resurrection: cats stay dead 5-10s with floating skull, then hurt animation plays 3× before waking up
- Wall grab sliding: cats slide slowly downward while grabbing a wall, then let go
- Sprite preview tool:
python3 tools/sprite_preview.py— visual grid of all cats, animations, overlays, and bubbles - Fix: meow bubble and overlay positioning (relative to cat head, not bounding box top)
- 6 pre-colored characters replacing the old single-sprite + color-tinting system: orange, tabby, dark, brown, grey, black (80×80 catset sprites)
- New animation states: flat/sit, love loaf, grooming, rolling, surprised, jumping, climbing
- Climbing mechanic: cats climb walls with pixel-accurate floor/centroid measurement so transitions to the next animation are seamless
- Settings rework: click a cat sprite to select it and see its name (editable), personality trait, and description
- Walk directions: east/west only — catset sprites don't have north/south/diagonal walk frames
- Removed: old color-tinting system, drinking, playing ball, butterfly, scratching tree, peeing, pooping animations (not in catset)
- Fix: click-to-chat bubble not triggering on Wayland (GDK input region was skipped)
- Fix: meow bubble squished — height now based on actual Pango text metrics, not a hardcoded
24px
- Speech bubbles now render emoji correctly via PangoCairo (COLRv1 fonts)
- Chat bubble no longer overflows screen edges — clamps and wraps text properly
- When two cats cross paths they stop, face each other, and exchange 1–3 AI-generated lines before going their separate ways
- Encounter bubbles shown above each cat with pixel-art style
- Up to 6 simultaneous cats, each with a distinct color and AI personality
- Drag cats anywhere on screen
- Persistent chat memory per cat across sessions
- Single desktop cat with pixel-art animations
- AI chat via Claude or Ollama
- Click-through transparent window (XShape)
- French / English / Spanish support





