What OpenPets is
OpenPets is a small open-source desktop app that puts an animated pet on your screen and lets your AI coding assistant react through it. When the assistant is thinking, editing, running tests, waiting for approval, or finishing a task, the pet shows a matching reaction or a short status message.
OpenPets is local-first. The desktop app, the MCP server, and the CLI all talk to each other over a local socket on your machine. The only outbound network calls the app makes are to download pets you choose to install and to check for OpenPets updates. There is no telemetry.
OpenPets works with any MCP-capable assistant. It has dedicated, one-click
integrations for Claude Code and OpenCode;
other clients such as Cursor, VS Code, Windsurf, and Zed can connect using
the standard stdio MCP command shown on the
How the docs are organized
This single page contains the full documentation, stacked top to bottom.
Each section is also a standalone page you can link directly — for example
/docs/concepts or /docs/mcp. The sidebar on the
left groups anchors by section so you can jump anywhere.
| If you want to… | Read |
|---|---|
| Get started in five minutes | |
| Understand the mental model | |
| Install the desktop app | |
| Connect Claude Code or OpenCode | |
| Install reminders, timers, or GitHub notifications | |
| Wire up a different MCP client | |
| Look up CLI commands | |
| Package or submit a pet | |
| Build or validate a desktop plugin | |
| Know exactly what OpenPets writes on disk or sends over the network | |
| Diagnose something that is not working |
Where to start
- If you have not installed OpenPets yet, start with
Quick start . - If you have it installed and want to connect an assistant, jump to
Set up AI assistants . - If you are an integrator, plugin author, or just curious about the architecture, read
How OpenPets works first.
1. Install the desktop app
Download the latest OpenPets release for your operating system and install it. The installers are unsigned today, so each operating system asks you to confirm the first launch.
| Platform | Download | First-launch note |
|---|---|---|
| macOS | OpenPets-<version>-mac-<arch>.dmg |
Drag to Applications. If Gatekeeper blocks the app, right-click the icon and choose Open the first time. |
| Windows | OpenPets-<version>-win-x64-setup.exe |
Run the installer and pick a destination. Approve the SmartScreen warning if it appears. |
| Linux | OpenPets-<version>-linux-<arch>.AppImage · .deb · .tar.gz |
For AppImage, mark it executable (chmod +x) and run it. |
See
2. Pick a pet
When OpenPets launches it lives in the system tray, not on the dock. Click the tray icon and open Pet Manager. You can install pets from the bundled gallery or browse all available pets at openpets.dev/gallery.
- Click Install on the pet you like.
- The desktop app downloads the pet package and extracts it locally.
- Once installed, set it as the default if you want it to appear at startup.
Installation is local. The desktop app fetches the catalog and the pet ZIP
over HTTPS, validates them, and writes them under your user data
directory. Nothing else leaves your machine. See
3. Connect your assistant
Open the tray menu, choose Integrations, and pick your AI assistant. OpenPets ships dedicated cards for Claude Code and OpenCode.
| Assistant | What "Install" does |
|---|---|
| Claude Code | Adds an MCP server called openpets to Claude at user scope, and writes a small managed instructions file so Claude knows when to use OpenPets. |
| OpenCode | Writes the OpenPets MCP entry, OpenCode plugin, and managed instructions into your global OpenCode config. |
Other MCP-capable assistants (Cursor, Windsurf, VS Code MCP, Zed,
Claude Desktop, etc.) don't have a dedicated card yet. To wire them up,
use the standard stdio command shown on
Hooks are not installed by the basic flow. Hooks are an opt-in extra that modifies your assistant's hook settings file to produce automatic reactions. Open Configure on the Claude Code card if you want to enable them.
4. Verify it works
Restart your assistant after installing the integration so it picks up the new MCP server. Then ask it to check OpenPets:
Can you call openpets_status and tell me which pet I have configured?
The assistant should call the openpets_status tool and reply
with the running app version and the targeted pet. You can also try
openpets_react and openpets_say to see the pet
change reaction or show a brief message.
If the assistant says OpenPets is unavailable, check that the desktop app
is still running in the tray and that you restarted the assistant after
installing the integration. See
Next steps
- Read
How OpenPets works to understand default vs agent pets, leases, and what reactions are available. - For deeper Claude Code setup including hooks, see
the Claude Code integration guide . - For OpenCode setup including the plugin, see
the OpenCode integration guide . - For everything else (CLI, file format, exact files OpenPets writes), keep scrolling or use the sidebar.
Default vs agent pets
OpenPets distinguishes between two kinds of pet windows on your desktop.
| Kind | When it shows | Who controls it |
|---|---|---|
| Default pet | Always, unless you hide it from the tray menu. | You. The tray controls visibility, position, and pause state. |
| Agent pet | Only while an assistant is actively using OpenPets. | The assistant. The pet window opens when an assistant requests a specific pet, and closes when the assistant stops requesting it. |
| Built-in pet | Whenever the default pet is missing or broken. | OpenPets. This is the bundled fallback that ships with the app so it is always available. |
The default pet is the everyday companion. Agent pets are useful when you have many assistants running at once and want each to drive its own pet, or when you have a specific pet you want a specific assistant to use.
If an assistant requests a pet that is not installed or is broken, OpenPets routes the events to the default pet instead of failing. The status response includes a fallback reason so the assistant can tell what happened.
Leases and routing
An assistant does not send pet reactions directly. It first asks OpenPets for a lease on a pet. The lease is a short ticket that says "for the next few seconds, route my reactions and messages to this pet". Leases let several assistants share OpenPets without stepping on each other.
| Step | What happens |
|---|---|
| 1. Acquire | The assistant calls lease.acquire with an optional pet id. OpenPets returns a lease that lasts 15 seconds. |
| 2. First explicit lease | If this is the first active lease for a specific (non-default) pet, OpenPets opens that pet's window. |
| 3. Heartbeat | The assistant calls lease.heartbeat while it is still working. Each heartbeat extends the lease by another 15 seconds. |
| 4. React or say | Reactions and messages tagged with the lease id are routed to the leased pet. Reactions without a lease go to the default pet. |
| 5. Release or expire | When the assistant releases the lease, or 15 seconds pass with no heartbeat, the lease ends. When the last lease for a specific pet ends, OpenPets closes that pet's window. |
The MCP server included with OpenPets handles this transparently: it acquires a lease at startup, heartbeats every 5 seconds, and releases on shutdown. The lease id is automatically attached to every reaction and message it sends.
lease TTL 15 seconds
heartbeat (MCP) every 5 seconds
server sweep every 5 seconds (releases expired leases)
fallback default pet if requested pet is missing or broken
Local IPC
All OpenPets communication is on your machine. There is no network bus, no cloud queue, no remote endpoint. The desktop app exposes a small local socket that the CLI, the MCP server, and any other OpenPets client connect to directly.
| Platform | Transport |
|---|---|
| macOS, Linux | Unix domain socket inside a private 0700 runtime directory. |
| Windows | Named pipe scoped to the user session. |
When OpenPets starts, it writes a small discovery file that tells clients where the socket lives and which token to present. Clients read the file, open the socket, send a token-stamped JSON request, and read a single JSON response.
macOS ~/Library/Application Support/OpenPets/runtime/ipc.json
Windows %APPDATA%\OpenPets\runtime\ipc.json
Linux $XDG_RUNTIME_DIR/openpets/ipc.json (if private)
~/.config/OpenPets/runtime/ipc.json (fallback)
The discovery file is written with permissions 0600 inside a
0700 directory. The token rotates every time the desktop app
starts.
Protocol shape
- Protocol version:
1. - Encoding: line-delimited JSON. One JSON request per connection, terminated by
\n. - Maximum message size: 16 KB.
- Connect timeout: 2 seconds. Response timeout: 3 seconds.
- Every request includes the discovery token. Wrong or missing token is rejected.
Available methods: hello, status,
pets.list, pets.install,
lease.acquire, lease.heartbeat,
lease.release, pet.react, and
pet.say. See the
MCP and hooks
There are two ways an assistant can drive your pet. MCP is the active path where the assistant calls tools deliberately. Hooks are the passive path where lifecycle events trigger reactions automatically.
| Path | Direction | What it can do | Available on |
|---|---|---|---|
| MCP tools | Assistant calls OpenPets. | Check status, change reaction, show a short message. | Any MCP-capable assistant. |
| Hooks | Assistant lifecycle event triggers OpenPets. | Automatic reactions for thinking, editing, testing, waiting, success, and error. | Claude Code today. Optional, installed separately. |
Most users keep MCP on and hooks off. Hooks add automatic visual feedback without the assistant having to call tools, but they also touch the assistant's hook settings file, which is why they are an opt-in extra rather than part of the basic install.
Both paths use the same local IPC under the hood, and both go through leases when targeting a specific pet.
Pet assets
A pet is a small package containing one spritesheet and a metadata file. OpenPets reads pets from three places.
| Source | What it is | Where it lives |
|---|---|---|
| Built-in pet | The bundled fallback that ships with every desktop release. | Inside the OpenPets app bundle. |
| Catalog pets | Pets you install from the OpenPets gallery. | Downloaded as ZIPs and extracted into the OpenPets user data directory. |
| Local Codex pets | Pets you are developing yourself, imported live for testing. | ~/.codex/pets/<pet-id>/ on your machine. |
Catalog ZIPs are validated before they are written to disk: archive size,
extracted size, number of entries, per-file size, path-traversal attempts,
and symlinks are all checked. See
The user data directory location depends on your operating system; the
exact paths are listed in
Reactions
Every pet supports the same fixed set of reactions. Assistants pick one
from this list when they call openpets_react or include a
reaction with openpets_say.
| Reaction | Typical meaning |
|---|---|
idle | Doing nothing in particular. |
thinking | Reading or planning before acting. |
working | General-purpose "busy" reaction. |
editing | Modifying files. |
running | Executing a command or process. |
testing | Running tests. |
waiting | Blocked, waiting for the user's approval or input. |
waving | Greeting or attention. |
success | Task completed successfully. |
error | Something went wrong. |
celebrating | Bigger win than a normal success. |
A pet only needs to provide animation frames for the reactions it cares
about. Reactions it does not implement fall back to idle.
Download
OpenPets releases are published on GitHub. Each release contains one artifact per platform and architecture, named with the version and target. The download names always follow this shape:
OpenPets-<version>-mac-<arch>.dmg
OpenPets-<version>-mac-<arch>.zip
OpenPets-<version>-win-<arch>-setup.exe
OpenPets-<version>-win-<arch>-portable.exe
OpenPets-<version>-linux-<arch>.AppImage
OpenPets-<version>-linux-<arch>.deb
OpenPets-<version>-linux-<arch>.tar.gz
Each release also publishes a SHA256SUMS file you can use to
verify the download. The current OpenPets installers are not
code-signed, so each operating system will warn you on first
launch.
macOS
- Download the
.dmgmatching your Mac (Apple Silicon or Intel). - Open the DMG and drag OpenPets.app into Applications.
- The first time you launch the app, macOS Gatekeeper will block it because the build is unsigned. Right-click OpenPets.app and choose Open, then click Open in the dialog.
- If macOS keeps refusing, remove the quarantine attribute manually:
xattr -d com.apple.quarantine /Applications/OpenPets.app
OpenPets hides itself from the Dock on macOS by design. After launch you only see the pet (if enabled) and the tray icon in the menu bar.
Windows
Pick one of two Windows installers depending on how you want to install OpenPets:
| Installer | What it does |
|---|---|
NSIS setup (-setup.exe) |
Standard installer. You choose the install directory; OpenPets is installed per-user, not for all users. |
Portable (-portable.exe) |
Self-contained executable. Run it from any folder; nothing is installed system-wide. |
On first launch, Windows SmartScreen may show "Windows protected your PC" because the installer is unsigned. Click More info, then Run anyway.
Linux
Three Linux artifacts are published. Pick whichever matches your distro and packaging preference:
| Artifact | How to run |
|---|---|
.AppImage |
Make it executable, then run it. No system install required. |
.deb |
Install with sudo apt install ./OpenPets-<version>-linux-<arch>.deb. |
.tar.gz |
Extract and run the OpenPets binary directly. |
chmod +x OpenPets-*.AppImage
./OpenPets-*.AppImage
OpenPets uses a private runtime directory for its IPC socket. On systems
with a hardened $XDG_RUNTIME_DIR (mode 0700,
owned by your user) it places the socket there; otherwise it falls back
to /tmp/openpets-<uid>/.
First launch
The first time OpenPets starts it shows an onboarding window so you can pick a starting pet and decide whether to enable the default pet on startup. After onboarding finishes, the app sits in the system tray.
On macOS, you will not see OpenPets in the Dock — the app intentionally hides itself from the Dock so the pet is the only visible presence. Everything you can do lives in the tray menu.
If you ever close the app accidentally on Windows or Linux, click the tray icon or launch OpenPets again; it uses a single-instance lock to avoid running twice.
Updates
OpenPets checks for new releases on GitHub when it starts. If a newer version is available, the tray menu shows an "Update available" entry that links to the latest release.
Updates are not installed automatically. To update:
- Quit OpenPets from the tray menu.
- Download the new installer for your platform.
- Run it; your installed pets and preferences stay in place because they live in your user data directory, not in the app bundle.
Uninstall
Removing the app does not remove your installed pets, preferences, or integration files. Those live outside the app bundle so updates can preserve them. Delete them by hand if you want a fully clean uninstall.
| Platform | Remove the app | Optional: remove user data |
|---|---|---|
| macOS | Drag OpenPets.app from Applications to the Trash. | ~/Library/Application Support/OpenPets |
| Windows | Use Add or remove programs, or delete the portable executable. | %APPDATA%\OpenPets |
| Linux | Delete the AppImage, or uninstall the deb with sudo apt remove openpets. |
$XDG_RUNTIME_DIR/openpets · ~/.config/OpenPets |
If you previously installed the Claude Code or OpenCode integrations,
open OpenPets first and remove them from the Integrations window before
deleting the app. That way the managed entries in
~/.claude and your OpenCode config get cleaned up properly.
See
Browse the gallery
The OpenPets pet catalog is published as a static JSON file at
https://openpets.dev/pets/catalog.v3.json. The catalog page
at openpets.dev/gallery renders that file as a
grid of installable pets, each with a preview spritesheet, name, and
short description.
Each catalog entry is a small package containing:
- id — short slug (lowercase letters, digits, hyphens, underscores; up to 64 characters).
- displayName — human-readable name (up to 120 characters).
- description — short description (up to 500 characters).
- preview — HTTPS URL to a preview image on
openpets.dev/pets/*. - zip — HTTPS URL to the downloadable package on
zip.openpets.dev/pets/*.
Install from the desktop app
This is the easiest path. Click the tray icon, open Manage Pets..., then pick a pet and click Install.
- The desktop app fetches the catalog over HTTPS.
- It downloads the pet ZIP from
zip.openpets.dev. - It validates the ZIP and extracts it to your user data directory.
- The pet appears in your installed list and can be set as the default.
See
Install from the CLI
If the OpenPets desktop app is already running, you can install a pet from a terminal:
npx -y @open-pets/cli@latest install <pet-id>
The CLI talks to the running app over local IPC and asks it to perform the install. The desktop app does the actual download and validation; the CLI just relays the request and prints the result.
If the desktop app is not running, this command fails with a clear message. Use the standalone installer below for an alternative that works without the app.
Standalone installer
For CI, dotfile bootstraps, or any environment where you cannot expect
the OpenPets desktop app to be running, use the standalone
install-pet package:
npx -y install-pet <pet-id>
The standalone installer prefers the running app when available and falls back to a direct download otherwise. A direct install:
- Acquires a per-user lock file under your user data directory so two installers cannot run at once.
- Fetches the catalog and the pet ZIP over HTTPS.
- Validates everything before writing — see safety limits below.
- Extracts atomically into
<userData>/pets/<pet-id>/and updates the OpenPets state file.
Local Codex pets
If you are creating your own pets, you can drop them into
~/.codex/pets/ and the desktop app picks them up
automatically — no catalog round-trip, no ZIP packaging.
~/.codex/pets/<pet-id>/
├── pet.json
├── spritesheet.webp
└── preview.webp # optional
The folder name must match the id field in pet.json
exactly. See pet.json schema and size limits.
Codex pets are intended for in-development testing. Once you are happy with one, publish it to the gallery — see the pet submission template in the OpenPets GitHub repo.
Manage installed pets
The Pet Manager window lists every installed pet and lets you:
- Set a pet as the default (the one shown at startup, when enabled).
- Show or hide the default pet from the tray.
- Pause all pets (handy when you are presenting or recording).
- Uninstall a pet you no longer want.
The built-in pet cannot be uninstalled — it is the fallback OpenPets uses if your selected default is missing or broken.
Safety and limits
Pet ZIPs are validated before anything is written to disk. The validation is identical whether the install goes through the desktop app or the standalone installer.
| Limit | Value |
|---|---|
| Catalog file size | 1 MB |
| ZIP download size | 50 MB |
| Extracted total size | 200 MB |
| File count per pet | 500 |
| Individual file size | 100 MB |
| Fetch timeout | 30 seconds |
On top of the size limits, the validator rejects symlinks, paths that
escape the pet directory, unsupported compression methods, encrypted
entries, and case-collision filenames. Pet ids that match the reserved
name builtin are rejected.
Overview
OpenPets connects to assistants through two layers:
| Layer | What it does | Required? |
|---|---|---|
| MCP server | Gives the assistant the openpets_status, openpets_react, and openpets_say tools so it can drive the pet deliberately. |
Yes — installed by every integration. |
| Lifecycle hooks | Hook into the assistant's lifecycle events so the pet reacts automatically when the assistant starts thinking, edits a file, runs a test, asks for permission, or finishes a task. | No — opt-in extra, Claude Code today. |
Both layers go through the same local IPC and the same lease lifecycle
described in
Claude Code
Open the desktop app's Integrations window and click Install
on the Claude Code card. That installs the user-scope MCP server and
writes a small managed instructions file at ~/.claude/openpets.md,
imported from ~/.claude/CLAUDE.md via a marker block.
Hooks are a separate opt-in action on the same card. They modify
~/.claude/settings.json to add OpenPets-managed command
entries for each Claude lifecycle event (UserPromptSubmit,
PreToolUse, PermissionRequest, Notification,
Stop, StopFailure).
For everything else — what the managed JSON looks like, how the hook
reaction map works, per-project setup with openpets configure,
troubleshooting — see the dedicated
OpenCode
OpenCode integration adds three things to your OpenCode global config: an OpenPets MCP entry, an OpenPets plugin reference, and a managed instructions block.
The OpenCode plugin hooks into the editor's event bus — chat messages, tool execution events — and maps them to pet reactions, much like Claude hooks do. Because it runs inside the OpenCode runtime, no settings file changes are required beyond enabling the plugin in config.
See
Other MCP clients
Any assistant that supports stdio MCP servers can use OpenPets. Add the
OpenPets server to your client's MCP config — the exact command and JSON
examples are on
Cursor, VS Code (with the MCP plugin), Windsurf, Zed, and Claude Desktop all work this way. Some clients require a restart after you add the OpenPets entry; check your client's MCP setup notes if the tools do not appear.
Pet routing
By default, every assistant sends reactions and messages to the
OpenPets default pet — the one you chose as default in the tray menu.
If you want a specific assistant to drive a specific installed pet
instead, pass --pet <pet-id> to the MCP command.
- Without
--pet: everything routes to the default pet. - With
--pet: a separate "agent pet" window opens while the assistant is active and closes when the assistant stops. - If the requested pet is missing, broken, or invalid, OpenPets routes events to the default pet instead and surfaces the fallback reason in status responses.
The desktop app's Integrations card has a Pet routing
selector that adds --pet for you. If you wire up an MCP
client by hand, add the flag yourself.
Removing integrations
Open the Integrations window and click Remove integration on the relevant card. That removes the OpenPets-managed MCP entry and instructions file from your assistant's config.
Hooks are removed separately from the same card because they live in a different settings file. If you only want the automatic reactions to stop, remove just the hooks; the MCP server stays installed.
For project-local setups installed via
openpets configure.claude/settings.local.json
or .opencode/ directory.
Overview
The openpets CLI is the same code that ships inside the
desktop app, packaged so you can invoke it from a terminal too. It does
four things:
| Command | Purpose |
|---|---|
openpets install <pet-id> | Install a pet through the running desktop app. |
openpets configure | Set up Claude Code or OpenCode for a specific project. |
openpets mcp | Start the OpenPets MCP server (used by integrations). |
openpets hook | Run one Claude hook event from stdin (used by integrations). |
You normally won't run openpets mcp or openpets hook
yourself. They are the entry points the Claude Code and OpenCode
integrations write into your assistant's config when you click
Install in the desktop app.
How to invoke
The CLI is published as @open-pets/cli on npm. There is no
separate installer — the desktop app does not put an openpets
binary on your PATH. Invoke it with npx:
npx -y @open-pets/cli@latest <subcommand> [...args]
If you want a permanent openpets command, install the package
globally with npm install -g @open-pets/cli. The bare
openpets ... syntax used in the Usage blocks below describes
the subcommand shape; substitute npx -y @open-pets/cli@latest
for openpets if you haven't installed it globally.
Run any subcommand with -h or --help for a
summary.
openpets install
Installs a pet from the OpenPets catalog by sending the request to the running desktop app over local IPC. The desktop app downloads the pet ZIP, validates it, and adds it to your installed pets.
openpets install <pet-id>
<pet-id>must match the pet id regex (lowercase letters, digits, hyphens, underscores; 1–64 characters; cannot bebuiltin).- The OpenPets desktop app must be running. If it is not, the install fails — use the standalone install-pet package for a direct download path that does not require the app.
- Install requests use a longer 60-second response timeout to accommodate larger pet downloads.
openpets configure
Sets up an AI assistant for a specific project. This is different
from the user-scope install you get from the desktop app's Integrations
window: openpets configure writes project-local config so the
setup ships with the repository.
openpets configure [--agent claude|opencode] [--pet <id>] [--cwd <path>]
[--yes] [--force] [--local-dev]
Options
| Flag | Description |
|---|---|
--agent <agent> | Which assistant to configure: claude or opencode. Defaults to claude. |
--pet <id> | Pet id to target. If omitted, the CLI prompts you with the list of installed pets (interactive shells only). |
--cwd <path> | Project directory to configure. Defaults to the current directory. |
--yes, -y | Accepted for scripts; no confirmation prompt is shown. |
--force, --replace | Replace any existing OpenPets-managed entries for this project, instead of leaving them alone. |
--local-dev | Use local development command paths instead of the published npx command. Useful when working on OpenPets itself. |
-h, --help | Show command help. |
What configure writes
For Claude projects:
- An
openpetsMCP entry at project scope, added viaclaude mcp add-json. - OpenPets-managed hook entries in
<project>/.claude/settings.local.jsonfor all Claude lifecycle events. - Hook commands include the
--openpets-managedmarker and the--project-localflag.
For OpenCode projects:
- An
openpetsMCP entry in the project's OpenCode config. - An OpenPets plugin reference.
- A managed instructions file under the project's
.opencode/directory.
The OpenCode setup is committable — you can check it into your repo and teammates get OpenPets in that project automatically once they have the desktop app installed.
Safety
- The project directory cannot be a symlink and must be a real directory.
- If
.claudeor.claude/settings.local.jsonexists, they cannot be symlinks and must stay within the project root. - Settings files are written atomically (temp file plus rename) with permissions
0600. - Claude must be on
PATH— the CLI callsclaude --versionbefore doing anything.
openpets mcp
Starts the OpenPets MCP server. The CLI spawns the
@open-pets/mcp entry as a child process with stdio
forwarded, so the MCP client (Claude Code, Cursor, etc.) talks to it
directly through stdin and stdout.
openpets mcp [--pet <id>]
--pet <id>targets a specific installed pet. Without it, events go to the default pet.- You normally don't run this yourself. The Integrations setup writes a command that effectively runs
openpets mcp --pet <id>into your assistant's MCP config. - The server acquires a lease on startup, heartbeats every 5 seconds, and releases it on
SIGINTorSIGTERM.
See
openpets hook
Runs a single Claude Code hook event. Claude pipes one JSON event to stdin and waits for the command to exit; OpenPets reads the event, decides on a reaction or short message, and exits quietly.
openpets hook --openpets-managed [--project-local] [--pet <id>]
--openpets-managedis a marker the CLI looks for when uninstalling or replacing hooks. It must be present on every OpenPets hook entry.--project-localsignals that this hook was installed at project scope (in.claude/settings.local.json). Project-local hooks are tracked separately so user-scope hooks can skip events that the project already handled.--pet <id>targets a specific pet.- The hook command never prints to stdout (so Claude does not accidentally ingest it as context) and always exits with code 0 unless OpenPets itself is misconfigured. If the desktop app is closed, the hook exits successfully and silently.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. |
1 | Usage error, configuration error, or unexpected failure. The CLI prints the error message to stderr. |
openpets hook is the deliberate exception: it tries hard to
exit 0 even when the desktop app is unavailable, so Claude sessions are
never blocked by an OpenPets problem.
Environment variables
| Variable | Effect |
|---|---|
OPENPETS_DEBUG=1 |
Enables verbose debug logging from the hook command. Useful when diagnosing why hooks are not reaching the pet. |
OPENPETS_DISCOVERY_FILE |
Overrides the default discovery-file path. Useful for tests, or when running OpenPets with a non-standard runtime directory. |
Overview
OpenPets ships a Model Context Protocol (MCP) server that lets any MCP-capable assistant drive your desktop pet. It is a small, purpose-built surface: check status, change the pet's reaction, show a short message. Nothing else.
The server runs as a local stdio process. Your MCP client launches it, pipes JSON-RPC over stdin/stdout, and OpenPets forwards the requests to the desktop app over local IPC.
MCP client (Claude, Cursor, Zed, ...)
-> stdio
-> @open-pets/mcp server
-> @open-pets/client (token-stamped local IPC)
-> OpenPets desktop app
-> default pet or leased agent pet
Quick install
If your assistant has a dedicated OpenPets integration card (Claude Code or OpenCode today), use that — it writes the correct command for your install method and handles the managed instructions for you. Otherwise add OpenPets to your client manually as a stdio MCP server.
For Claude Code specifically:
claude mcp add --scope user openpets -- npx -y @open-pets/mcp@latest
Add --pet <pet-id> after @open-pets/mcp@latest if
you want to target a specific installed pet.
Server command
The server binary is published as @open-pets/mcp on npm and
is also bundled inside the desktop app. There are two command shapes
depending on whether you are using the published package or the bundled
one inside an OpenPets release.
Published package
npx -y @open-pets/mcp@latest [--pet <pet-id>]
Bundled inside the desktop app
The OpenPets Integrations window writes a path-based command that points inside the installed app bundle, so it works without internet access after installation. On macOS the command looks like:
node /Applications/OpenPets.app/Contents/Resources/app.asar.unpacked/node_modules/@open-pets/mcp/dist/index.js
Flags
| Flag | Meaning |
|---|---|
--pet <pet-id> | Request a specific installed pet for this MCP process. Missing pets fall back to the default. |
--pet=<pet-id> | Same as above with the equals form. |
--help, -h | Show help and exit. |
--version, -v | Print the package version and exit. |
Pet ids are limited to 128 bytes UTF-8 and cannot contain control characters, slashes, or backslashes. The id must also match the standard pet id regex (lowercase letters, digits, hyphens, underscores).
Client config examples
The JSON snippets below show the OpenPets MCP entry for several popular
clients. Replace <pet-id> with the pet you want, or
omit the flag to target the default pet.
Claude Code
{
"mcpServers": {
"openpets": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@open-pets/mcp", "--pet", "<pet-id>"]
}
}
}
Cursor, Windsurf, generic stdio MCP
{
"mcpServers": {
"openpets": {
"command": "npx",
"args": ["-y", "@open-pets/mcp"]
}
}
}
Bundled-app path
Use this when you want to skip npx and run the MCP server
that ships inside an installed OpenPets desktop app.
{
"mcpServers": {
"openpets": {
"type": "stdio",
"command": "node",
"args": [
"/Applications/OpenPets.app/Contents/Resources/app.asar.unpacked/node_modules/@open-pets/mcp/dist/index.js"
]
}
}
}
Tools
The server registers three tools. Their input schemas are enforced server-side with zod; invalid input returns a tool error without contacting the desktop app.
| Tool | Purpose | Annotations |
|---|---|---|
openpets_status | Check whether OpenPets is reachable, which pet is currently targeted, and whether the lease is healthy. | read-only, idempotent |
openpets_react | Set a short coding-oriented reaction on the targeted pet. | side-effects, non-idempotent |
openpets_say | Show a short safe message on the targeted pet (with optional reaction). | side-effects, non-idempotent |
Input schemas
{}
{
"reaction": "thinking"
}
{
"message": "Building the pet manager",
"reaction": "working"
}
Reactions and speech
Reactions are picked from a closed list. See
idle, thinking,
working, editing, running,
testing, waiting, waving,
success, error, and celebrating.
Speech messages are short. The server enforces these rules on every
openpets_say call:
- 1 to 140 characters after trimming.
- Single line — carriage returns and newlines are rejected.
- No code-like patterns (backticks,
<script, function definitions, JS keywords likeclass,import,const). - No URLs or file paths (HTTP URLs,
www., slash paths, Windows drive letters). - No secret-like text (
api_key,secret,token,password, PEM headers).
Reject reasons come back as plain text tool errors. The pet never receives anything that fails validation.
Validation and safety
The MCP server is built to be a benign visible-status channel. It intentionally cannot read your editor state, your project files, or your assistant's transcript.
| Protection | How it works |
|---|---|
| Server instructions | The MCP server tells the assistant to use openpets_say only for short user-facing status messages. |
| Schema validation | Every openpets_react and openpets_say input is validated with zod before leaving the server. |
| Speech validation | Messages that look like code, URLs, paths, or secrets are rejected before reaching the desktop app. |
| Error sanitization | Low-level errors (IPC paths, socket names, tokens, system error codes) are stripped from tool-error responses. The MCP client sees a short "OpenPets desktop app or local IPC is unavailable" message instead of internals. |
| No network | The MCP server itself only talks to the desktop app over a local socket; it never reaches the network. |
Pet targeting and leases
Pet targeting works through leases. See
- The server acquires a lease at startup, passing the optional
--pet <id>as the requested pet. - The desktop app picks an actual pet — the requested one if it is installed and usable, otherwise the default pet, with a fallback reason attached.
- Every reaction and message includes the lease id, so events route to the leased pet even when the default pet is also visible.
- The server heartbeats every 5 seconds; if a heartbeat fails, the lease is marked degraded and tool calls return a clear "lease unavailable" error.
- On
SIGINTorSIGTERM, the server releases the lease before shutting down.
If the requested pet is missing, broken, or invalid, the lease is still
granted but it points at the default pet. The structured status response
surfaces this through fallbackReason so a careful assistant
can warn the user.
IPC discovery
The MCP server uses the standard OpenPets discovery file to find the
desktop app. See
Two things to be aware of when running the MCP server in unusual environments:
- The discovery file's
platformfield must match the platform of the MCP process. Running the macOS-built MCP server from a Linux VM against a macOS-built desktop app will fail discovery validation. - Set
OPENPETS_DISCOVERY_FILEto override the discovery path — useful when the OpenPets desktop app is running for a different user, or when scripting tests.
Troubleshooting
The MCP server starts but every tool returns "OpenPets is unavailable"
- Make sure the OpenPets desktop app is running.
- Check that the discovery file exists at the expected path for your platform.
- If you restarted the desktop app, the discovery token rotated. Restart the MCP client too so it picks up the new lease.
Tools return "OpenPets lease is unavailable"
- The startup lease failed or expired. Most often this means the desktop app was not running when the MCP server started.
- Restart the MCP client. The server tries to re-acquire a lease on startup.
The pet does not change reaction even though calls "succeed"
- Check whether the default pet is paused from the tray menu. Paused pets accept reactions silently.
- If you passed
--pet <id>and the structured status shows afallbackReason, the requested pet is missing or broken — reinstall it.
openpets_say messages get rejected
- The validation is strict for safety. Remove code-looking content, URLs, paths, or secret-like words and try again.
- If you are sending a stack trace, error log, or file path through the pet, do not. Use a separate channel.
See
What plugins are
OpenPets plugins are small, optional JavaScript add-ons that run inside the desktop app. They can schedule friendly reminders, react to local time, or call approved HTTPS endpoints through the OpenPets SDK, then ask your default pet to speak or react.
Plugins are not AI-assistant integrations. They do not connect to your editor, MCP client, shell, filesystem, or private project files. Install only the plugins you want, and turn them off at any time from the desktop tray.
Official first-party plugins
| Plugin | What it does |
|---|---|
| Daily Reminders | Shows lightweight scheduled reminders such as breaks, hydration, or stand-up nudges. |
| Pomodoro | Runs focus and break timers with pet reactions at each transition. |
| GitHub Notifications | Checks configured public GitHub repositories for visible activity and lets your pet notify you. |
Manage plugins
Open the desktop tray menu and choose Plugins. From there you can browse the catalog, install official plugins, update installed plugins, change settings, enable or disable a plugin, or uninstall it.
- Open the OpenPets desktop tray menu.
- Select Plugins.
- Choose a plugin from the catalog and click Install.
- Review its permissions and approved network hosts.
- Use Configure for plugin-specific settings.
- Use the enable toggle to start or stop the plugin without uninstalling it.
- Use Update when a newer version is available, or Uninstall to remove it.
Permissions and network access
Every catalog plugin declares the capabilities it needs in its manifest. OpenPets validates those permissions before the plugin is installed and again before it runs. Network access is limited to approved HTTPS hosts; plugins cannot open arbitrary URLs.
The current GitHub Notifications plugin supports public repositories only. It does not use OAuth, does not ask for a GitHub token, and cannot read private repositories. Because it uses unauthenticated GitHub API requests, GitHub may rate-limit checks if you configure many repositories or refresh very often.
Troubleshooting
- If a plugin stops working, open Tray → Plugins, disable it, then enable it again.
- Use Update to make sure the installed ZIP matches the latest catalog version.
- Check the plugin's configuration for missing required fields or unsupported repository names.
- If OpenPets marks a plugin as broken, uninstall it and install it again from the catalog.
- For reports, include the plugin name, version, what you expected, and the relevant desktop log lines. Do not include tokens, private repository names, or project paths.
Package layout
A pet is a small folder containing a metadata file and a spritesheet.
For the gallery, the folder is zipped up and hosted on
zip.openpets.dev.
<pet-id>/
├── pet.json
├── spritesheet.webp
└── preview.webp # optional, used as a thumbnail
The folder name must equal the id inside pet.json.
There is no special "root" inside the ZIP — the entries live directly at
the top level of the archive.
pet.json
Metadata describing the pet. All fields are required unless marked optional.
{
"id": "my-pet",
"displayName": "My Pet",
"description": "A friendly companion that loves test suites.",
"spritesheetPath": "spritesheet.webp"
}
| Field | Type | Constraints |
|---|---|---|
id |
string | Lowercase letters, digits, hyphens, underscores. Must start with a letter or digit. 1–64 characters. Cannot be builtin. Must match the folder name. |
displayName |
string | Non-empty after trimming. Maximum 80 characters. |
description |
string | Non-empty after trimming. Maximum 500 characters. |
spritesheetPath |
string | Must be the literal string "spritesheet.webp". |
Spritesheet
The spritesheet is a single WebP image at the file named in
spritesheetPath. It contains the animation frames for every
reaction your pet supports.
A pet does not have to implement every reaction. Reactions that have no
frames in the spritesheet fall back to idle. See the
Look at the source of any existing gallery pet (or the built-in pet that ships with OpenPets) for an example sprite layout. There is no required cell size — the desktop renderer reads frame coordinates from the pet's spritesheet metadata.
Validation rules
OpenPets validates every pet before it is written to disk. Some checks apply to the ZIP file as a whole; some apply to each entry inside.
ZIP-level limits
- Downloaded ZIP must be no larger than 50 MB.
- Extracted total size must be no larger than 200 MB.
- No more than 500 entries.
- Individual file size must be no larger than 100 MB.
- The ZIP magic bytes are verified before extraction begins.
Per-entry rules
- Paths must stay inside the pet directory — no
..traversal. - No symlinks, no encrypted entries, no unsupported compression methods.
- Case collisions are rejected (so a pet cannot contain both
image.pngandImage.png). - Entry Unix mode must be a valid file or directory mode.
Required files after extraction
A pet must contain pet.json and the file named by
spritesheetPath. If either is missing, the install is
rejected and nothing is written to your installed-pets directory.
Codex (local-dev) pets
During development you do not need to package a pet as a ZIP. Drop the
folder into ~/.codex/pets/ and the desktop app imports it
live.
Codex pets have slightly different limits because they are unzipped on disk:
| Limit | Value |
|---|---|
| Maximum codex pets imported | 100 |
pet.json file size | 128 KB |
| Spritesheet size | 100 MB |
| Preview image size | 8 MB |
| Total preview bytes across all pets | 24 MB |
The folder name in ~/.codex/pets/ must match the
id inside pet.json. Reload OpenPets after
changing files in a codex pet folder.
Publishing a pet
Once your pet is ready, submit it to the OpenPets gallery. There is a pet-submission issue template in the OpenPets GitHub repo; open an issue with the required metadata and attach (or link to) the spritesheet. Accepted pets are added to the catalog and become installable from inside the desktop app.
Tray-only design
OpenPets has no traditional main window. The app lives in your system tray (Windows / Linux) or menu bar (macOS). On macOS, the app deliberately hides itself from the Dock so the only visible presence is your pet.
Every action — managing pets, configuring integrations, changing settings, quitting — is reached through the tray menu or one of the short-lived "task" windows it opens.
Pet windows
Each pet renders inside its own transparent, frameless, always-on-top Electron window. The window has no chrome at all — it is just the spritesheet with a transparent background and animation driven by CSS.
The default pet uses one window that persists across the session. Agent pets each get their own short-lived window that opens when an assistant requests them and closes when the assistant stops.
Pet windows track which screen they are on and remember their position between launches.
Drag and click-through
You can drag any pet window around your desktop with the mouse. Outside of dragging, the pet window is click-through by default, so it does not intercept clicks meant for whatever is beneath it.
The drag affordance and the click-through toggle are wired through a
small preload script that exposes a narrow renderer API via Electron's
contextBridge. The renderer itself runs sandboxed with
context isolation, no Node integration, and a strict
default-src 'none' CSP.
Task windows
Task windows are short-lived Electron windows opened from the tray menu. They show a focused UI for one task and close when you are done.
| Window | Purpose |
|---|---|
| Pet Manager | Browse the gallery, install or uninstall pets, pick the default pet. |
| Integrations | Install, configure, and remove Claude Code and OpenCode integrations. |
| Settings | Adjust pet scale, speech-bubble preference, and other preferences. |
| Onboarding | First-launch flow for choosing a starting pet and enabling startup display. |
Onboarding
The first time OpenPets starts, the onboarding window opens automatically. Once you finish (or skip) onboarding, the app marks it as completed in your state file and stops showing the window at every launch.
If you skipped onboarding and want to come back to it, click Continue Setup... in the tray menu while the onboarding state is still pending.
Update checks
On startup, OpenPets queries the GitHub Releases API for the
alvinunreal/openpets repository to see whether a newer
version is available. The check runs once, in the background, with a
short timeout.
If a newer version is found, the tray menu adds an
Update available entry that opens the release page in
your browser. Nothing is downloaded or installed automatically. See
Single-instance behaviour
OpenPets uses Electron's single-instance lock. If you launch the app while it is already running, the second launch exits immediately and the running instance brings its tray menu's most recent task window to the front.
This avoids accidental duplicate trays, duplicate IPC servers, and duplicate update prompts.
User data directory
OpenPets keeps state, installed pets, and runtime metadata in a single user data directory. The location is platform-specific.
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/OpenPets |
| Windows | %APPDATA%\OpenPets |
| Linux | $XDG_CONFIG_HOME/OpenPets (or ~/.config/OpenPets) |
You can override the path by setting OPENPETS_USER_DATA in
your environment. The override applies to the standalone installer and
to anything that uses the user data path; the desktop app itself uses
Electron's app.getPath("userData"), which respects its own
standard rules.
App state file
The app state file openpets-state.json lives at the root of
the user data directory. It records:
- The selected default pet id.
- Whether the default pet opens at launch and whether speech bubbles are enabled.
- The pet scale preference.
- Whether onboarding has been completed.
- Optional command paths for Claude and OpenCode (if you set custom binaries).
- The list of installed pets and their metadata.
- The default pet's last-known position.
All writes to openpets-state.json are atomic: OpenPets
writes to a temporary file alongside the original and renames it into
place. There is no partial-write state.
Installed pets
Each installed gallery pet lives under <userData>/pets/<pet-id>/.
The directory contains the extracted pet package — pet.json,
spritesheet.webp, and any optional preview image.
Locally-developed Codex pets live separately under
~/.codex/pets/<pet-id>/. The desktop app reads both
locations.
IPC discovery file
When the desktop app starts, it writes a small JSON file that tells
clients (the MCP server, the CLI, hooks) where to connect and which
token to use. The file has permissions 0600 inside a
0700 directory.
| Platform | Discovery path |
|---|---|
| macOS | ~/Library/Application Support/OpenPets/runtime/ipc.json |
| Windows | %APPDATA%\OpenPets\runtime\ipc.json |
| Linux (secure XDG) | $XDG_RUNTIME_DIR/openpets/ipc.json |
| Linux (fallback) | ~/.config/OpenPets/runtime/ipc.json |
On Linux, the secure XDG location is used only if
$XDG_RUNTIME_DIR is a real directory you own with mode
0700. Otherwise OpenPets falls back to the path above.
The discovery file contains a freshly-generated token every time the desktop app starts. Old tokens become invalid immediately.
Lock files
- Single-instance lock — Electron's built-in lock; held automatically while the desktop app is running.
- Direct install lock — used by the standalone
install-petpackage to prevent two concurrent installs. Lives at<userData>/.install-pet.lock. Stale locks are considered expired after 10 minutes. - Unix socket — on macOS and Linux, the actual IPC socket file under
/tmp/openpets-<uid>/or$XDG_RUNTIME_DIR/openpets/. Removed when the app exits cleanly.
Claude-managed files
When you install the Claude Code integration from the desktop app,
OpenPets manages three files under ~/.claude/:
| File | What OpenPets writes |
|---|---|
~/.claude/CLAUDE.md |
One managed import block bracketed by <!-- OPENPETS:IMPORT:START --> / :END -->. Existing instructions are preserved. |
~/.claude/openpets.md |
The OpenPets instruction file itself, bracketed by <!-- OPENPETS:START --> / :END -->. |
~/.claude/settings.json |
OpenPets-managed hook command entries (only when you opt in to hooks). Each managed command contains the --openpets-managed marker. |
Claude's MCP config is written through the claude mcp add-json
command rather than by editing a file directly; the entry is named
openpets at user scope.
Project-local Claude setup (via openpets configure) writes
to <project>/.claude/settings.local.json instead.
OpenCode-managed files
The OpenCode integration writes to OpenCode's global config directory,
typically ~/.config/opencode/. It manages three things
inside the OpenCode config file, plus a separate instructions file:
| Where | What OpenPets writes |
|---|---|
OpenCode config (mcp section) |
An openpets stdio MCP entry. |
OpenCode config (plugin array) |
A reference to the OpenPets OpenCode plugin so it loads at editor startup. |
OpenCode config (instructions array) |
A reference to the OpenPets instructions file. |
.opencode/openpets.md (project) or the corresponding global path |
The OpenPets instructions, bracketed by <!-- OPENPETS:START --> / :END -->. |
OpenCode config edits use a JSONC parser so existing comments and trailing commas survive. Writes are atomic with backups. The integration refuses to write through symlinks.
Local-first by design
OpenPets runs entirely on your machine. The desktop app, the MCP server, the CLI, and every assistant integration talk to each other over a local socket — never the network. There is no cloud backend, no OpenPets account, no remote orchestration.
The only outbound network traffic OpenPets ever makes is the small set of HTTPS calls listed below.
Network calls
| Endpoint | When | Why |
|---|---|---|
https://openpets.dev/pets/catalog.v3.json |
When you open the Pet Manager or run a CLI install. | To fetch the pet catalog so you can browse and install pets. |
https://zip.openpets.dev/pets/<pet-id>.zip |
When you install a specific pet. | To download that pet's package. |
https://openpets.dev/plugins/catalog.v2.json |
When you open the Plugins panel. | To fetch the plugin catalog so you can install or update approved plugins. |
https://zip.openpets.dev/plugins/<plugin-id>.zip |
When you install or update a plugin. | To download that plugin's package. |
https://openpets.dev/pets/<pet-id>/preview.webp |
When the gallery is open. | To render the pet's thumbnail. |
https://api.github.com/repos/alvinunreal/openpets/releases |
Once at startup, with a short timeout. | To check whether a newer OpenPets release exists. Nothing is downloaded automatically. |
https://api.github.com/repos/<owner>/<repo> |
Only when the GitHub Notifications plugin is enabled and configured with public repositories. | To check visible public repository activity. The plugin does not use OAuth, tokens, or private repository access. |
Catalog URLs that do not point at approved OpenPets catalog and ZIP hosts are rejected by the validator before any request is made. The catalog is also rejected if it tries to embed credentials, custom ports, or non-HTTPS URLs.
No telemetry
OpenPets does not collect usage analytics, crash reports, or any other telemetry. There is no opt-out toggle because there is nothing to disable. The desktop app makes only the calls listed above and nothing else.
Speech validation
OpenPets is designed as a benign visible-status channel, not a
transcript sink. Every openpets_say message goes through a
server-side validator that rejects anything that looks like sensitive
or accidentally-leaked content.
- 1–140 characters, single line.
- No code-like content (backticks,
function,class,import,const,=>,<script). - No URLs (
http://,https://,www.). - No file paths (slash paths, Windows drive letters).
- No secret-like words (
api_key,secret,token,password, PEM headers).
The validator runs inside the MCP server, before the message is sent to the desktop app. Even if an assistant ignores its instructions, a bad message is rejected with a plain error.
Why OpenPets avoids the keychain
On macOS and Linux, Chromium (which Electron uses) tries to access the OS credential store at startup to encrypt browser-style passwords and cookies. OpenPets stores none of those, so the credential prompt would be confusing.
The app passes two startup flags to Chromium —
--use-mock-keychain and --password-store=basic —
to disable that behaviour. OpenPets does not have a Keychain entry,
does not request one, and does not need one.
Tokens and sockets
The local IPC is gated by a token written to a private discovery file. Each desktop app startup generates a new random 32-byte token. Old tokens stop working immediately on restart.
- On macOS and Linux, the IPC socket and the discovery file live inside
0700directories and are themselves0600. - On Windows, the named pipe is scoped to the current user session.
- Other users on the same machine cannot connect to your OpenPets without your token.
Tool errors that bubble up to the MCP client are sanitized — IPC paths, socket names, tokens, and low-level system error codes are stripped before they ever reach the assistant.
The desktop app is not running
Most "OpenPets is unavailable" errors mean the desktop app is closed or crashed.
- Check the system tray (or menu bar on macOS) for the OpenPets icon.
- If the icon is missing, launch the app again. The single-instance lock will route a second launch to the existing one if it is still running.
- If you see the icon but tools still fail, the discovery token may have rotated. Restart your MCP client so it acquires a fresh lease.
Claude does not show OpenPets tools
- Restart Claude Code after installing or replacing the MCP configuration.
- In OpenPets, open Integrations and click Refresh on the Claude card.
- Run
claude --versionin a terminal. The CLI must be onPATHfor OpenPets to detect Claude. If it lives elsewhere, set a full path under Advanced detection. - Verify the MCP entry with
claude mcp listandclaude mcp get openpets.
OpenCode does not react
- Make sure OpenPets is running. The OpenCode plugin uses the same local IPC, so a closed desktop app means no reactions.
- Confirm the OpenPets plugin entry is still listed in your OpenCode config. The Integrations window can re-add it if it was removed.
- Reactions are throttled. If you have already seen one reaction recently, the next event may be silenced — this is intentional and not a bug.
MCP entry is reported as custom
"Custom" means Claude (or OpenCode) already has a server named
openpets but its command does not match what OpenPets would
write today. OpenPets leaves it alone in that case to avoid stomping on
your customization.
To force OpenPets to recreate the entry, open the integration card and click Replace configuration. Your previous custom command will be overwritten.
A pet fails to install
- Check the pet id. Lowercase letters, digits, hyphens, underscores; up to 64 characters; cannot be
builtin. - Make sure the pet exists in the gallery. Open openpets.dev/gallery and verify the id.
- Check your network. The catalog and ZIP downloads require HTTPS access to
openpets.devandzip.openpets.dev. - If the standalone
install-petreports a stale lock, wait 10 minutes for the lock to expire or remove<userData>/.install-pet.lockby hand after confirming no other install is running. - Validation can reject malformed pets. The error message names the rule that failed — symlinks, oversized files, missing
pet.json, missing spritesheet, etc.
The pet is not showing
- From the tray menu, check Show Default Pet. If it reads Hide Default Pet, the pet is already shown — look at other monitors.
- If pets are paused (Resume All Pets in the tray menu), unpause them.
- If you requested a specific pet via
--pet, look at the structured status response. AfallbackReasonmeans the pet is missing or broken and events went to the default instead. - Reinstall the affected pet from the Pet Manager.
Update check fails
The update check hits the GitHub Releases API and times out quickly if your network is slow or restricted. A failed check is non-fatal — the tray menu simply omits the Update available entry.
- If you suspect you are out of date, visit the OpenPets GitHub releases page directly.
- Corporate networks sometimes block
api.github.com. The check will fail silently if so.
Reporting an issue
If something is still broken after walking through this page, please open an issue at the OpenPets GitHub repo. Include:
- Your operating system and OpenPets app version (visible in the Settings window).
- The exact steps to reproduce.
- Which assistant and integration are involved (Claude Code, OpenCode, etc.).
- For hook problems, the output of running the hook command with
OPENPETS_DEBUG=1.
Please do not paste private code, secrets, or full transcripts into issues. A short error message and the steps to reproduce are usually enough.
Repository layout
OpenPets is a pnpm workspace with two top-level workspace patterns: one for deployable apps and one for publishable packages.
openpets/
├── apps/
│ └── desktop/ # Electron tray app
├── packages/
│ ├── agent-events/ # Speech pools + validation
│ ├── claude/ # Claude Code integration
│ ├── cli/ # `openpets` CLI
│ ├── client/ # Local IPC client
│ ├── install-pet/ # Standalone pet installer
│ ├── mcp/ # MCP server
│ ├── opencode/ # OpenCode integration
│ └── pet-format/ # Pet package marker type
└── web/ # openpets.dev (Nuxt 4)
Every workspace package is ESM ("type": "module") and
written in TypeScript 6, targeting Node ≥ 20.
Setup
Install pnpm 11 and Node 20+, then:
pnpm install
pnpm build # build all workspace packages
pnpm dev:desktop # run the desktop app in dev mode
The desktop app depends on the workspace packages via
workspace:*, so a fresh build of the packages is required
before dev:desktop can launch it.
Common commands
| Command | What it does |
|---|---|
pnpm build | Build every workspace package. |
pnpm check | Run each package's check script (lint, format, etc.). |
pnpm typecheck | Run each package's typecheck script. |
pnpm test | Build, then run each package's test script (contract checks live alongside source as check-*.ts). |
pnpm dev:desktop | Run the Electron desktop app in development. |
pnpm dev:desktop:plugins | Run the desktop app with local plugin development support. |
pnpm package:desktop:dir | Produce an unpacked desktop build at apps/desktop/dist-electron/. |
pnpm package:desktop | Run the full electron-builder packaging for your current platform. |
pnpm release:desktop | Orchestrate a local multi-platform release (see below). |
pnpm release:npm | Publish workspace packages to npm. |
Testing
OpenPets uses lightweight contract-style tests rather than a heavy test
framework. Every package has one or more check-*.ts files
that exercise the package's public surface end-to-end and assert
expected behaviour.
- Contract files run during
pnpm testat the workspace root. - The checks intentionally cover the IPC protocol, lease lifecycle, ZIP safety, catalog validation, and other boundary modules.
- They are deliberately fast and deterministic — there is no network, no Electron startup, and no test runner harness.
Plugin development
Desktop plugins use manifest v2 and a bundled JavaScript entry file. The
manifest declares the plugin id, version, entry point, configuration
schema, permissions, and approved HTTPS hosts. At runtime the entry calls
OpenPetsPlugin.register({ start, stop }) so the desktop app can
start and stop it cleanly.
OpenPetsPlugin.register({
async start(ctx) {
await ctx.pet.say('Plugin started')
},
async stop(ctx) {
await ctx.pet.react('idle')
},
})
The SDK exposes the supported plugin surface: pet speech and reactions, configuration reads, timers, logging, and HTTPS requests to hosts approved by the manifest. Plugins do not get Node APIs, shell execution, direct filesystem access, or arbitrary networking.
For local development, point the desktop app at plugin folders or manifest files before launching it:
OPENPETS_DEV_PLUGIN_ROOTS=/path/to/plugin-root pnpm dev:desktop:plugins
OPENPETS_DEV_PLUGIN_PATHS=/path/to/openpets.plugin.json pnpm dev:desktop:plugins
Before submitting catalog changes, validate the generated web catalog without uploading ZIP files:
node scripts/sync-plugins.js --dry-run --skip-r2
Safety limits are part of the contract: no Node, shell, or filesystem access; HTTPS only; and host allowlisting enforced through the SDK.
Release: desktop app
Desktop releases are produced from macOS by a single orchestrator
script at apps/desktop/scripts/release-local.mjs. It
runs preflight checks, builds, and produces installers for macOS,
Windows, and Linux from one machine.
Default build plan:
| Platform | Default artifact |
|---|---|
| macOS | DMG (universal x64 + arm64) |
| Windows | NSIS installer (x64) |
| Linux | AppImage (x64) |
Optional flags add a macOS ZIP, a Windows portable executable, Linux
.deb or tarball builds, or experimental ARM64 builds for
Windows and Linux. After build, the script generates a
SHA256SUMS file and, with --yes, creates a
GitHub draft release and uploads the artifacts.
Release: npm packages
Workspace packages are published independently. Run
pnpm release:npm from the repo root; the orchestrator
builds, runs checks, and publishes each package whose version has
changed since its last publish.
All packages currently share the 2.0.x version line but
have independent package.json versions, so you can bump
one without rev'ing the rest.
Where to read next
The internal architecture is documented through per-directory codemap files inside the repository. They are intended for contributors, not end users:
codemap.mdat the repo root for a high-level workspace map.apps/desktop/src/codemap.mdfor the Electron app's module layout.packages/<name>/src/codemap.mdfor each package's responsibilities.
For the public-facing surface of every package, see
Overview
OpenPets is split into small focused npm packages so integrations and
third-party tools can depend on only the pieces they need. Inside the
monorepo they are linked via workspace:*; on npm they are
independently versioned.
| Package | Use when… |
|---|---|
@open-pets/client | You want to talk to the OpenPets desktop app directly from Node. |
@open-pets/cli | You want the openpets CLI for installs, configure, MCP, and hooks. |
@open-pets/mcp | You want the OpenPets MCP server as a standalone binary. |
@open-pets/claude | You are building a Claude Code integration or running Claude hooks programmatically. |
@open-pets/opencode | You are building an OpenCode plugin or managing OpenCode configs. |
@open-pets/agent-events | You need the shared speech pools and validator without the rest of the stack. |
install-pet | You need a zero-dep CLI to install a pet without the OpenPets desktop app. |
@open-pets/pet-format | You want a type-level marker for OpenPets-compatible pet packages. |
@open-pets/client
The core IPC client. Every other OpenPets package that talks to the desktop app uses this.
- Discovery file reading, endpoint validation, token-stamped requests.
createOpenPetsClient()factory returninghello,status,listPets,installPet,acquireLease,heartbeatLease,releaseLease,react, andsay.- Strongly typed protocol, custom
OpenPetsClientErrorwith stable error codes.
npm install @open-pets/client
@open-pets/cli
The published npm CLI package. See
npx -y @open-pets/cli@latest configure --agent claude
# optional permanent shell command:
npm install -g @open-pets/cli
@open-pets/mcp
The OpenPets MCP server. Stdio transport, three tools, lease-aware
lifecycle. See
npx -y @open-pets/mcp@latest [--pet <pet-id>]
@open-pets/claude
Claude Code integration package. Contains the hook runtime, hook settings manager, and MCP configuration helpers used by the desktop app and the CLI.
runClaudeHookFromStdin()— execute one Claude hook event.installClaudeHooks(),uninstallClaudeHooks(),doctorClaudeHooks()— manage~/.claude/settings.json.buildClaudeMcpPreview(),parseClaudeMcpGetOutput(),classifyClaudeMcpStatus()— inspect Claude MCP config.
@open-pets/opencode
OpenCode integration package. Ships the OpenPets OpenCode plugin and the project/global config managers.
plugin.ts— default export consumed by OpenCode.prepareOpenCodeProjectSetup()/writePreparedOpenCodeProjectSetup()— atomic project-level config writes.prepareOpenCodeGlobalSetup()/writePreparedOpenCodeGlobalSetup()— global config writes used by the desktop app.
@open-pets/agent-events
Shared speech pools and validator. Both the Claude hook runtime and the OpenCode plugin use this so the messages they emit feel consistent and pass the same safety rules.
hookSpeechPools— readonly record keyed by category (thinking,success,error,permission).pickHookSpeech(category, randomFn)— pick one message safely.validateHookSpeech(message)— reject unsafe-looking content.
install-pet
Standalone CLI to install a single pet. Tries the running desktop app first; falls back to a direct download if the app is unavailable.
npx -y install-pet <pet-id>
Useful in dotfiles, CI bootstraps, and anywhere you cannot assume the OpenPets desktop app is open.
@open-pets/pet-format
Tiny marker package exporting a nominal type and a constant for packages that conform to the OpenPets pet format. Use it when you build tooling on top of OpenPets and want to identify pet packages at the type level.
The actual pet-package schema lives in the desktop app's catalog
validator and the Codex pet validator — see
Code license
OpenPets source code is released under the MIT License. The license applies to the desktop application, every workspace package, and the website source.
MIT LicenseCopyright (c) 2026 OpenPets
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND...
Pet assets
Pet spritesheets and preview images are not covered by the OpenPets code license. Each pet ships with its own metadata; pet creators retain the rights to their artwork unless they explicitly relicense it.
If you want to redistribute or remix a specific pet, check the
licensing notes on that pet's gallery entry or its
pet.json metadata before doing so.
Third-party software
OpenPets stands on a number of excellent open-source projects. Major runtime dependencies include:
- Electron — desktop app shell.
- @modelcontextprotocol/sdk — MCP server implementation.
- yauzl — streaming ZIP extraction for pet packages.
- jsonc-parser — JSONC-aware OpenCode config edits.
- zod — runtime schema validation in the MCP server.
- highlight.js — code highlighting on this documentation site.
- Nuxt — the framework powering openpets.dev.
Each dependency is governed by its own license. See the source code in the OpenPets GitHub repository for the full list and the corresponding license texts.

