Documentation

OpenPets Docs

OpenPets is a desktop pet app for coding agents and AI assistants. It shows a small animated pet on your desktop and lets tools such as Claude Code, OpenCode, Cursor, VS Code, Windsurf, Zed, or any MCP-capable assistant update the pet while work is happening.

This page covers installation, pet setup, assistant integration, local files, troubleshooting, and the developer architecture.

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 MCP reference page.

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 minutesQuick start
Understand the mental modelHow OpenPets works
Install the desktop appInstall the desktop app
Connect Claude Code or OpenCodeSet up AI assistants
Install reminders, timers, or GitHub notificationsPlugin guide
Wire up a different MCP clientMCP server reference
Look up CLI commandsCLI reference
Package or submit a petPet file format
Build or validate a desktop pluginDeveloper plugin notes
Know exactly what OpenPets writes on disk or sends over the networkLocal files · Privacy
Diagnose something that is not workingTroubleshooting

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.

PlatformDownloadFirst-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 Install the desktop app for the full per-platform walkthrough, including how to handle Gatekeeper and SmartScreen, and how to uninstall.

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 Privacy & network behaviour for the exact request list.

3. Connect your assistant

Open the tray menu, choose Integrations, and pick your AI assistant. OpenPets ships dedicated cards for Claude Code and OpenCode.

AssistantWhat "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 MCP server reference.

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:

Prompttext
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 Troubleshooting if it still does not connect.

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.

KindWhen it showsWho 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.

StepWhat 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 lifetimestext
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.

PlatformTransport
macOS, LinuxUnix domain socket inside a private 0700 runtime directory.
WindowsNamed 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.

Discovery file pathtext
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 reference for the full schema each one accepts and returns.

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.

PathDirectionWhat it can doAvailable 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.

SourceWhat it isWhere 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 Pet file format for the package layout and validation rules.

The user data directory location depends on your operating system; the exact paths are listed in Local files & configuration.

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.

ReactionTypical meaning
idleDoing nothing in particular.
thinkingReading or planning before acting.
workingGeneral-purpose "busy" reaction.
editingModifying files.
runningExecuting a command or process.
testingRunning tests.
waitingBlocked, waiting for the user's approval or input.
wavingGreeting or attention.
successTask completed successfully.
errorSomething went wrong.
celebratingBigger 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:

Artifact namingtext
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

  1. Download the .dmg matching your Mac (Apple Silicon or Intel).
  2. Open the DMG and drag OpenPets.app into Applications.
  3. 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.
  4. If macOS keeps refusing, remove the quarantine attribute manually:
Terminalbash
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:

InstallerWhat 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:

ArtifactHow 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.
Terminalbash
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:

  1. Quit OpenPets from the tray menu.
  2. Download the new installer for your platform.
  3. 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.

PlatformRemove the appOptional: 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 Local files & configuration for the complete list of files OpenPets touches.

Install from the desktop app

This is the easiest path. Click the tray icon, open Manage Pets..., then pick a pet and click Install.

  1. The desktop app fetches the catalog over HTTPS.
  2. It downloads the pet ZIP from zip.openpets.dev.
  3. It validates the ZIP and extracts it to your user data directory.
  4. The pet appears in your installed list and can be set as the default.

See Local files & configuration for the exact directory where installed pets live on each platform.

Install from the CLI

If the OpenPets desktop app is already running, you can install a pet from a terminal:

Terminalbash
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:

Terminalbash
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.

Layouttext
~/.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 file format for the full 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.

LimitValue
Catalog file size1 MB
ZIP download size50 MB
Extracted total size200 MB
File count per pet500
Individual file size100 MB
Fetch timeout30 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:

LayerWhat it doesRequired?
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 How OpenPets works.

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 Claude Code integration page.

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 the OpenCode integration page for the global vs project setup details, the JSONC-aware config writes, and how the managed instruction block looks.

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 MCP server reference.

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, delete or rewrite the project's .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:

CommandPurpose
openpets install <pet-id>Install a pet through the running desktop app.
openpets configureSet up Claude Code or OpenCode for a specific project.
openpets mcpStart the OpenPets MCP server (used by integrations).
openpets hookRun 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:

Run a subcommandbash
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.

Usagebash
openpets install <pet-id>
  • <pet-id> must match the pet id regex (lowercase letters, digits, hyphens, underscores; 1–64 characters; cannot be builtin).
  • 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.

Usagebash
openpets configure [--agent claude|opencode] [--pet <id>] [--cwd <path>]
                  [--yes] [--force] [--local-dev]

Options

FlagDescription
--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, -yAccepted for scripts; no confirmation prompt is shown.
--force, --replaceReplace any existing OpenPets-managed entries for this project, instead of leaving them alone.
--local-devUse local development command paths instead of the published npx command. Useful when working on OpenPets itself.
-h, --helpShow command help.

What configure writes

For Claude projects:

  • An openpets MCP entry at project scope, added via claude mcp add-json.
  • OpenPets-managed hook entries in <project>/.claude/settings.local.json for all Claude lifecycle events.
  • Hook commands include the --openpets-managed marker and the --project-local flag.

For OpenCode projects:

  • An openpets MCP 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 .claude or .claude/settings.local.json exists, 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 calls claude --version before 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.

Usagebash
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 SIGINT or SIGTERM.

See MCP server reference for the complete tool surface, schemas, and integration with other MCP clients.

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.

Usagebash
openpets hook --openpets-managed [--project-local] [--pet <id>]
  • --openpets-managed is a marker the CLI looks for when uninstalling or replacing hooks. It must be present on every OpenPets hook entry.
  • --project-local signals 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

CodeMeaning
0Success.
1Usage 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

VariableEffect
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.

Runtime pathtext
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 (user scope)bash
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

Commandbash
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:

macOS bundled commandbash
node /Applications/OpenPets.app/Contents/Resources/app.asar.unpacked/node_modules/@open-pets/mcp/dist/index.js

Flags

FlagMeaning
--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, -hShow help and exit.
--version, -vPrint 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

~/.claude/mcp.json (excerpt)json
{
  "mcpServers": {
    "openpets": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@open-pets/mcp", "--pet", "<pet-id>"]
    }
  }
}

Cursor, Windsurf, generic stdio MCP

mcp.jsonjson
{
  "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.

mcp.json (bundled)json
{
  "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.

ToolPurposeAnnotations
openpets_statusCheck whether OpenPets is reachable, which pet is currently targeted, and whether the lease is healthy.read-only, idempotent
openpets_reactSet a short coding-oriented reaction on the targeted pet.side-effects, non-idempotent
openpets_sayShow a short safe message on the targeted pet (with optional reaction).side-effects, non-idempotent

Input schemas

openpets_statusjson
{}
openpets_reactjson
{
  "reaction": "thinking"
}
openpets_sayjson
{
  "message": "Building the pet manager",
  "reaction": "working"
}

Reactions and speech

Reactions are picked from a closed list. See Reactions for the full table; the allowed values are 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 like class, 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.

ProtectionHow 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 Leases and routing for the underlying lifecycle. Inside the MCP server it works like this:

  1. The server acquires a lease at startup, passing the optional --pet <id> as the requested pet.
  2. 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.
  3. Every reaction and message includes the lease id, so events route to the leased pet even when the default pet is also visible.
  4. The server heartbeats every 5 seconds; if a heartbeat fails, the lease is marked degraded and tool calls return a clear "lease unavailable" error.
  5. On SIGINT or SIGTERM, 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 Local IPC for the file format and per-platform paths.

Two things to be aware of when running the MCP server in unusual environments:

  • The discovery file's platform field 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_FILE to 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 a fallbackReason, 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 Troubleshooting for broader symptoms that are not specific to the MCP path.

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

PluginWhat it does
Daily RemindersShows lightweight scheduled reminders such as breaks, hydration, or stand-up nudges.
PomodoroRuns focus and break timers with pet reactions at each transition.
GitHub NotificationsChecks 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.

  1. Open the OpenPets desktop tray menu.
  2. Select Plugins.
  3. Choose a plugin from the catalog and click Install.
  4. Review its permissions and approved network hosts.
  5. Use Configure for plugin-specific settings.
  6. Use the enable toggle to start or stop the plugin without uninstalling it.
  7. 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.

Layouttext
<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.

pet.jsonjson
{
  "id": "my-pet",
  "displayName": "My Pet",
  "description": "A friendly companion that loves test suites.",
  "spritesheetPath": "spritesheet.webp"
}
FieldTypeConstraints
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 reactions list for the full set.

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.png and Image.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:

LimitValue
Maximum codex pets imported100
pet.json file size128 KB
Spritesheet size100 MB
Preview image size8 MB
Total preview bytes across all pets24 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.

Tray menu

The tray menu shows different items depending on app state. The full set of items, in order:

ItemShows whenWhat it does
OpenPetsAlways.Disabled header label.
Update available...A newer release exists on GitHub.Opens the release page in your browser.
Continue Setup...Onboarding was never completed.Reopens the onboarding window.
Default Pet: ...Always.Opens the Pet Manager. Shows the currently selected default pet's name.
Show / Hide Default PetAlways.Toggles the default pet's window.
Pause / Resume All PetsAlways.Pauses all pet reactions and speech bubbles globally.
Manage Pets...Always.Opens Pet Manager.
Integrations...Always.Opens the Integrations window.
Settings...Always.Opens the Settings window.
Quit OpenPetsAlways.Quits the app, releasing any active leases and removing the IPC discovery file.

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.

WindowPurpose
Pet ManagerBrowse the gallery, install or uninstall pets, pick the default pet.
IntegrationsInstall, configure, and remove Claude Code and OpenCode integrations.
SettingsAdjust pet scale, speech-bubble preference, and other preferences.
OnboardingFirst-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 Updates for the manual update steps.

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.

PlatformPath
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.

PlatformDiscovery 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-pet package 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/:

FileWhat 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:

WhereWhat 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

EndpointWhenWhy
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 0700 directories and are themselves 0600.
  • 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

  1. Restart Claude Code after installing or replacing the MCP configuration.
  2. In OpenPets, open Integrations and click Refresh on the Claude card.
  3. Run claude --version in a terminal. The CLI must be on PATH for OpenPets to detect Claude. If it lives elsewhere, set a full path under Advanced detection.
  4. Verify the MCP entry with claude mcp list and claude 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.dev and zip.openpets.dev.
  • If the standalone install-pet reports a stale lock, wait 10 minutes for the lock to expire or remove <userData>/.install-pet.lock by 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. A fallbackReason means 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.

Layouttext
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:

Terminalbash
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

CommandWhat it does
pnpm buildBuild every workspace package.
pnpm checkRun each package's check script (lint, format, etc.).
pnpm typecheckRun each package's typecheck script.
pnpm testBuild, then run each package's test script (contract checks live alongside source as check-*.ts).
pnpm dev:desktopRun the Electron desktop app in development.
pnpm dev:desktop:pluginsRun the desktop app with local plugin development support.
pnpm package:desktop:dirProduce an unpacked desktop build at apps/desktop/dist-electron/.
pnpm package:desktopRun the full electron-builder packaging for your current platform.
pnpm release:desktopOrchestrate a local multi-platform release (see below).
pnpm release:npmPublish 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 test at 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.

Plugin entryjs
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:

Terminalbash
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:

Terminalbash
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:

PlatformDefault artifact
macOSDMG (universal x64 + arm64)
WindowsNSIS installer (x64)
LinuxAppImage (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.

The internal architecture is documented through per-directory codemap files inside the repository. They are intended for contributors, not end users:

  • codemap.md at the repo root for a high-level workspace map.
  • apps/desktop/src/codemap.md for the Electron app's module layout.
  • packages/<name>/src/codemap.md for each package's responsibilities.

For the public-facing surface of every package, see Package reference.

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.

PackageUse when…
@open-pets/clientYou want to talk to the OpenPets desktop app directly from Node.
@open-pets/cliYou want the openpets CLI for installs, configure, MCP, and hooks.
@open-pets/mcpYou want the OpenPets MCP server as a standalone binary.
@open-pets/claudeYou are building a Claude Code integration or running Claude hooks programmatically.
@open-pets/opencodeYou are building an OpenCode plugin or managing OpenCode configs.
@open-pets/agent-eventsYou need the shared speech pools and validator without the rest of the stack.
install-petYou need a zero-dep CLI to install a pet without the OpenPets desktop app.
@open-pets/pet-formatYou 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 returning hello, status, listPets, installPet, acquireLease, heartbeatLease, releaseLease, react, and say.
  • Strongly typed protocol, custom OpenPetsClientError with stable error codes.
Installbash
npm install @open-pets/client

@open-pets/cli

The published npm CLI package. See CLI reference for every command.

Installbash
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 MCP server reference.

Runbash
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.

Install one petbash
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 Pet file format.

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.

LICENSEtext
MIT License

Copyright (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.

by Alvin