---
## Why this one
Most desktop pets are either just an animated picture, or they steal your attention while you work. Three hard rules here:
| Principle | How it is implemented |
|---|---|
| **Never interrupt** | While tools are running the mascot **never speaks and never switches poses randomly**; all chatter, care prompts and announcements only happen when idle |
| **Never steal focus** | Decorative by default: `aria-hidden`, no keyboard capture, no writes to business DOM, no modifications to bundled packages. Even keyboard focus requires you to opt into accessibility mode |
| **No privacy tax** | No telemetry, no uploads, zero external requests by default. Everything that reads conversation content or account amounts (TTS playback, keyword reactions, balance, accessibility) is **off by default** |
And she actually knows what you are doing: instead of switching artwork on a timer, she reads the host's structural markers (tool-card state, session-running marker, terminal blocks) to decide which pose to hold and when to stay quiet.
---
## Table of Contents
- [Quick start](#quick-start)
- [Screenshots](#screenshots)
- [Features](#features)
- [Compatibility](#compatibility)
- [First run](#first-run)
- [Usage](#usage)
- [Settings panel](#settings-panel)
- [Update / Rollback / Uninstall](#update--rollback--uninstall)
- [Version History](#version-history)
- [Data & Privacy](#data--privacy)
- [FAQ](#faq)
- [Troubleshooting](#troubleshooting)
- [Roadmap](#roadmap)
- [Project Structure](#project-structure)
- [Development & Testing](#development--testing)
- [Contributing](#contributing)
- [Credits](#credits)
- [License](#license)
---
## Quick start
```powershell
# DeepSeek Harness Desktop (Electron shell) — point the path at your install
$Desktop = "D:\DeepseekHarnessDesktop"
$Cli = "$Desktop\resources\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js"
$env:ELECTRON_RUN_AS_NODE = "1"
& "$Desktop\DeepSeek Harness.exe" --expose-internals $Cli plugin --profile desktop add github:Sutera-Diffusus/dsh-whale-musume
```
```powershell
# Legacy web profile
dsh plugin --profile web add github:Sutera-Diffusus/dsh-whale-musume
```
Then **restart the host** (restart the desktop app, or restart `dsh web` and hit `Ctrl+F5`). She shows up in the bottom-right corner.
> If the plugin store or `dsh plugin add` fails to install it, see [When the plugin store cannot install it](#when-the-plugin-store--dsh-plugin-add-cannot-install-it). A log that only shows `[exit 1]`, the expected miss on npm and fallback to git, and the `allowBuilds` note are all covered there.
| Idle companion | Work-state integration |
|---|---|
|

|

|
| Floats bottom-right; random sips of coffee, stretches | Detects running tools, picks up a laptop with a soft blue glow |
| Head-pat reaction | Feeding |
|---|---|
|

|

|
| Click her head: blush artwork + hearts and stars | Right-click → feed: satiety and affection go up |
| Floating form (draggable) | Artwork overview (90+) |
|---|---|
|

|

|
| 200px by default; drag her wherever you like | States / interactions / growth / minigames / weather / festivals / meme faces |
More: [interaction board](docs/images/actions-board.png) · [new artwork board](docs/images/new-poses-board.png) · posters [v1](docs/images/promo-poster-v1.png) / [v2](docs/images/promo-poster-v2.png) / [v3](docs/images/promo-poster-v3.png) / [v4](docs/images/promo-poster-v4.png)
---
## Features
### 🐋 Body & behaviour
| Feature | Detail |
|---|---|
| Five forms | Floating / side / bar / mini / auto; switches with the current view, 200px floating by default |
| Drag with inertia | Dragging switches to a "picked up" artwork that sways with the cursor; on release she slides a short distance and rotates upright, and grips the screen edge instead of spinning further. A gentle release only bounces once; the position is saved after the slide ends |
| Momentum-masked transitions | Idle ↔ work uses a "press down → swap artwork → pop up" transition with no ghosting and no black flash; a 1.6 s timeout covers browsers that pause image transitions in background tabs |
| Summon button | After you switch her off, a 🐋 button stays in the bottom-left corner so she can always be called back |
### 💼 Work-state integration
| Feature | Detail |
|---|---|
| Busy/idle reaction | Detects running tools and switches to "hugging a laptop", with a soft blue glow and a "working" label |
| Per-tool poses | Shell / file edits / search / tests / review / deploy / debug each map to a pose; anything unrecognised falls back to the generic working pose rather than guessing |
| Stable while working | No idle skits, no chatter during work; when a signal briefly disappears the pose holds (8 s on the workbench view, 4 s elsewhere) instead of twitching |
| Click reactions | While working, clicking her randomly shows "shy with laptop" or "snacking on a RAM stick" without interrupting the work state |
### 📈 Growth & achievements
- **Stats**: mood, affection, satiety, level, sign-in streak, companionship time
- **Daily quests**: three slots refreshed each day, each granting affection
- **Weekly sign-in**: seven-slot board with 1 / 3 / 7-day milestone rewards
- **Bond levels**: Lv3 unlocks a new idle action, Lv5 the title "Guardian of Whale Tide", Lv7 a hidden easter egg
- **45 achievements + achievement wall**: interaction / companionship / DSH usage / minigames / quests; unlocked ones highlighted, locked ones dimmed; new "Hundred-Day Pact", "One-Year Pact" and four memory-game achievements
- **Growth journal**: records bond level-ups and achievement unlocks, newest 12 shown first
### 💬 Dialogue & companionship
- **530+ lines** covering every scene, cute by default with safe meme flavours (office life, slacking off, deadlines, "drawing pies", unhinged-literature style lines)
- **13 meme keywords** (kyun, OMG, doge, sike, worship, peace, existential doubt, waku waku, …) turn her into the matching sticker face
- **Idle chatter every 5–8 minutes**, locally classified by what your current task looks like; never speaks during work, never greets between 23:00 and 05:59
- **Mood tiers**: gentle lines when mood is low, energetic ones when it is high; bond levels unlock their own lines
- **Weather companion**: leave the city empty for zero networking; otherwise uses Open-Meteo (free, no key) plus full-screen ambience (rain / snow / lightning / wind / fog / heat haze / frost), automatically toned down while working
- **Proactive care**: sitting reminder (25 min busily working) / late-night rest nudge / stuck-companion check (same state for 8 min) / welcome-back greeting (away over 3 min); at least 15 minutes between prompts
- **Two work-speech modes** (issue #24): thinking / tool / success / failure lines share one random cooldown window — **Chatty** 15–40 s, **Quiet** 90–240 s — so at most one line per window; thinking and tool lines are picked from the current task topic (same local classifier as idle chatter), while success / failure keep their own lines. Idle greetings and weather are unaffected; Chatty is the default
- **Optional TTS playback**: the "line playback" toggle only appears when the host provides a `dsh-xiaomi-tts` service; **off by default**, and a missing service, missing configuration or failed playback never affects normal interaction
### 🎀 Interactions & visuals
- **Zone clicks**: head / belly / tail are separate hit zones, each with its own artwork, effects and lines
- **Click effects**: hearts and stars fly out; three quick clicks trigger a starry-eyed celebration with particles and a spin
- **90+ artworks**: idle, working, thinking, away, four growth states, four minigame states, three weather states, five festival sets (Christmas / Halloween / Mid-Autumn / Lunar New Year / Valentine's, switched automatically on the day)
- **Three mini-games**: **Bubble Pop Party** (4×4 grid, normal / star / bomb, combo scoring, 30-second rounds), **Snack Catch** (catch falling snacks with keyboard or mouse, avoid bombs), and **Whale Memory** (4×4 memory match reusing 16 existing artworks; 60 seconds, combo bonus, zero-mistake clear); all three share the three reward-bearing rounds per day and are playable by mouse and keyboard
### 🧩 Engineering
- **Non-invasive**: pure frontend injection plus one read-only static asset route; no bundled package is modified
- **No build step**: the `assets/` directory in this repo *is* the runtime artifact
- **Artwork preloading**: the first screen loads only 5 common poses, the remaining 90+ are fetched one at a time every 120 ms while idle, so cold pose switches never stall; failures are silent
- **Accessibility mode** (off by default): Tab focus, Enter/Space to pat, arrow keys to nudge position (Shift to speed up), state changes announced through `aria-live`
- **Theme following**: follows the host's light/dark theme for bubbles and menus only — **artwork is never filtered**, so the art style is preserved
- **Testable**: the state machine and the presentation layer are separate; unit tests run with plain `node --test`
---
## Compatibility
| Host | Support | Notes |
|---|:---:|---|
| **DeepSeek Harness Desktop** `0.2.0-rc.2` | ✅ tested | That build's GUI defaulted to `127.0.0.1:19387`. Install and rollback: [0.2.0 deploy guide](docs/desktop-0.2.0-rc.2-deploy.md) |
| **DeepSeek Harness Desktop / Web** `0.2.1-rc.1`, `0.2.1-rc.2` | ✅ static contract | Checked against tag `dsh-v0.2.1-rc.2` and the unit tests. **Not** re-run inside the Electron app. Desktop 0.2.1 binds with `--port 0` (OS-assigned); do not assume `19387`. See the [0.2.1 contract](docs/desktop-0.2.1-rc.2-contract.md) and [deploy delta](docs/desktop-0.2.1-rc.2-deploy.md) |
| **Legacy web profile** (DSH `0.1.x`) | ✅ tested | `0.1.0-rc.6` and newer; the settings panel is verified on `0.1.1-rc.2` |
| Other profiles (headless / acp, …) | ➖ no UI | There is no browser surface, so there is nowhere to show a pet |
| Requirement | Value |
|---|---|
| OS | Windows 10 / 11 (the current desktop app's support range) |
| Node.js | 18+ (script install only; the bundle install needs nothing) |
| Browser | Recent Edge / Chrome |
> **Data does not carry over**: the desktop app's window origin is `dsh-app://app` while the legacy web UI is `http://127.0.0.1: