# dsh-plugin-pet A virtual pet companion for the DeepSeek Harness web GUI: built-in, custom, and Codex-style sprites with mood animations driven by live agent state. [中文](README.md) [![dsh-plugin topic](https://img.shields.io/badge/topic-dsh--plugin-blue)](https://github.com/topics/dsh-plugin) [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE) A desktop pet for **DeepSeek Harness Web GUI**, inspired by the Codex companion and Claude Code Buddy. The pet lives in the bottom-right corner and reacts in real time to the agent state (thinking / tool calls / errors / turn completed / waiting for you), with rename, scale, visibility, and full reskinning support: - **6 built-in sprites** (blob / cat / duck / robot / axolotl / ghost; inline SVG, pure CSS animation) - **Custom image upload** (PNG / JPEG / GIF / WebP; animated images play as-is) - **Codex-style multi-frame spritesheets** — import a Codex pet pack (`pet.json` + `spritesheet.webp`, V1 1536×1872 and V2 1536×2288; see the [Codex pet package spec](https://codexpet.xyz/spec/)), or configure a generic grid (columns / rows / fps / per-mood state rows) All state is stored under `$DSH_HOME/storages/dsh-plugin-pet/` and survives `dsh web` restarts. ## Structure | File | Purpose | | --- | --- | | `lib/index.js` | Host plugin: pet persistence + HTTP routes mounted on the harness `webServer` service | | `lib/client.js` | Browser bundle (hand-written factory-CJS, no build step): pet overlay and settings panel | | `test/` | `node --test` suite covering host routes and the zero-dependency image sniffer | This is a dual-face package: the host half is a plain Cordis plugin; the client half is declared through `"dsh": { "client": { "platform": "web", "inject": [], "immediately": true } }` plus the `"./client"` export, so the harness client-modules system serves it at `/plugins/dsh-plugin-pet/client.js` and loads it immediately at boot. ## Install Install into the web profile from GitHub (requires `pnpm` on `PATH`; otherwise use the corepack fallback below): ```sh npx @deepseek-ai/dsh plugin --profile web add "github:c-ling/dsh-plugin-pet#v1.0.3" ``` Or with an existing `dsh` binary: ```sh dsh plugin --profile web add "github:c-ling/dsh-plugin-pet#v1.0.3" ``` When `pnpm` is not on `PATH`: ```sh cd ~/.dsh/profiles/web corepack pnpm add "github:c-ling/dsh-plugin-pet#v1.0.3" ``` > `dsh plugin` forwards its arguments to pnpm and fetches the package from this repo > (pnpm 9+, `git` required). The warning > `declares no dsh.bundle — installed as a plain dependency` is expected: this plugin is > not a profile bundle layer; it is activated by the loader row below. Then add a loader row to `~/.dsh/profiles/web/cordis.patch.yml`: ```yaml - insert: - id: dsh-plugin-pet name: 'dsh-plugin-pet' config: maxImageBytes: 5242880 # optional: single-image upload limit (default 5 MB) maxSheetBytes: 10485760 # optional: spritesheet upload limit (default 10 MB) ``` Restart `dsh web` (client-modules caches package verdicts per process; new packages require a host restart), then hard-refresh the page. The pet appears in the bottom-right corner; its settings live under **Settings → Pet**. ## Verify ```sh curl -s http://127.0.0.1:3080/plugins/dsh-plugin-pet/client.js | head -c 60 ``` It should print a factory bundle starting with `window.__ModuleLoader__.load({`; the pet should be visible in the bottom-right corner. ## Update ```sh dsh plugin --profile web add "github:c-ling/dsh-plugin-pet#v1.0.3" # or: npx @deepseek-ai/dsh plugin --profile web add "github:c-ling/dsh-plugin-pet#v1.0.3" # or: cd ~/.dsh/profiles/web && corepack pnpm add "github:c-ling/dsh-plugin-pet#v1.0.3" ``` Re-running the install command with the new `#v1.0.3` pin upgrades the dependency; the loader row in `cordis.patch.yml` stays unchanged. Restart `dsh web`, then hard-refresh. ## Uninstall ```sh cd ~/.dsh/profiles/web corepack pnpm remove dsh-plugin-pet # or: dsh plugin --profile web remove dsh-plugin-pet ``` Remove the matching insert row from `cordis.patch.yml`, then restart `dsh web`. Uploaded data remains under `$DSH_HOME/storages/dsh-plugin-pet/` until you delete that directory manually. ## Using the pet - **Drag** the pet anywhere; the position is remembered per browser. - **Single-click** pets it (hearts pop out). **Double-click** hides it; click the paw button to bring it back. - Mood is derived from the live session state: idle, thinking, working (the bubble shows the current tool name), sad (prompt error), waiting (pending confirmation), plus a celebratory hop when a turn finishes. ## Codex spritesheet import 1. Go to **Settings → Pet → Spritesheet → Import a Codex pet pack**. 2. Upload `spritesheet.webp` / `spritesheet.png` first. It must be **1536×1872 (V1, 9 rows)** or **1536×2288 (V2, 11 rows)** — an 8-column grid with 192×208 cells. The version is auto-detected. 3. Optionally import `pet.json` — it applies `displayName` and verifies `spriteVersionNumber` against the uploaded spritesheet. Codex rows map to DSH moods as: idle→0, thinking→8 (review), working→7 (running), happy→4 (jumping), sad→5 (failed), waiting→6, petting→3 (waving). Direction rows (1/2) and V2 facing rows (9/10) are unused by this plugin. ## Generic spritesheet Under **Spritesheet → Generic spritesheet**, any PNG/WebP whose width and height divide evenly into the grid works: set columns, rows, and fps, upload the image, then assign a row and frame count to each mood. A live preview player animates the draft before saving. ## HTTP API | Method | Path | Purpose | | --- | --- | --- | | GET | `/dsh-plugin-pet/config` | Current effective pet configuration | | POST | `/dsh-plugin-pet/config` | Update name / size / visibility / builtin sprite / sheet fps, state rows, frame counts | | GET/POST/DELETE | `/dsh-plugin-pet/image` | Read / upload (raw bytes) / delete the custom image | | GET/POST/DELETE | `/dsh-plugin-pet/sheet` | Read / upload (raw bytes; `?source=codex&version=1\|2\|auto` or `?source=custom&cols=&rows=&fps=`) / delete the spritesheet | | POST | `/dsh-plugin-pet/petjson` | Import a Codex `pet.json` (requires a matching spritesheet already uploaded) | All uploads are validated server-side: magic-byte sniffing (PNG/JPEG/GIF/WebP), size limits, exact Codex dimensions, and even cell splitting for generic grids. Config writes are atomic. ## Development ```sh node --check lib/index.js lib/client.js node --test test/sniff.test.mjs test/host.test.mjs ``` The client bundle is pure JavaScript in the harness factory-CJS format (`window.__ModuleLoader__.load({ id: "dsh-plugin-pet", factory })`); `require("react")` resolves through the shell static module table. All UI contributions are registered via the `shell.overlay` and `settings.section` slots and managed by disposers held through `ctx.effect`. ## Known limitations - One global pet per page (root-scope overlay), not per session. - Codex “trailing empty frames” auto-detection is not implemented; frame counts come from the spec defaults or your explicit configuration. - The pet position is stored per browser (`localStorage`) and is not synced across devices. - Activating the plugin requires restarting the `dsh web` host process (the web profile disables Cordis HMR for host rows by default). ## License [MIT](LICENSE)