# DSH Desk Pet

<p align="center"><b>English</b> · <a href="README.md">简体中文</a></p>

<p align="center">
  <b>A macOS desk pet that shows you what your DSH agent is doing.<br>
  It can be your own cat.</b>
</p>

<p align="center">
  <img src="docs/media/diy-skin.png" width="820" alt="One photo of a cat becomes six animated pet states">
</p>
<p align="center">
  <sub>One photo in, six states out — idle, working, waiting, error, happy, sleeping.<br>
  Generated by your own image tool on your own credentials. Nothing is sent anywhere by us.</sub>
</p>

<p align="center">
  <img src="https://img.shields.io/badge/macOS-only-000000.svg?logo=apple&logoColor=white" alt="macOS only">
  <a href="https://www.npmjs.com/package/dsh-macos-pet"><img src="https://img.shields.io/npm/v/dsh-macos-pet.svg?color=cb3837&label=npm" alt="npm"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
  <img src="https://img.shields.io/badge/python-system%20only-3776AB.svg" alt="System Python">
  <img src="https://img.shields.io/badge/dependencies-none-2ea44f.svg" alt="No dependencies">
  <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/topic-dsh--plugin-111111.svg" alt="dsh-plugin"></a>
</p>

<p align="center">
  <img src="docs/media/floats-above.png" width="820" alt="The pet sitting on top of a fullscreen editor">
</p>
<p align="center">
  <sub>A real macOS window, not a widget inside the DSH page. It stays above everything you<br>
  work in, fullscreen Spaces included, and follows the agent without being told.</sub>
</p>

---

## Install

With DSH already set up, one command:

```bash
dsh plugin --profile web add dsh-macos-pet
dsh web
```

**Upgrading from an earlier version needs `@latest`:**

```bash
dsh plugin --profile web add dsh-macos-pet@latest
```

The bare form writes a `^0.x` range, and a caret on a `0.x` version pins the
minor — so `^0.1.0` never accepts `0.2.0`, and the plain command reports
"Already up to date" while leaving the old version in place.

The pet appears on your desktop, floating above whatever you are working in.
Nothing is added to the DSH page itself.

Pet only, no DSH: clone the repo and run `./bin/dsh-macos-pet`.

To follow the main branch instead of the published version:

```bash
dsh plugin --profile web add github:Dkrillex/dsh-macos-pet#main
```


**No dependencies.** Nothing to install first, nothing to build, no ffmpeg. It runs
on the Python that ships with macOS.

## Use

| | |
|---|---|
| **Drag** | Grab it anywhere. Where you leave it is where it starts next time. |
| **Click** | Opens the session list — which DSH sessions exist, which is live, what it is doing. Click again to close. |
| **Sleep (Do Not Disturb)** | Quiets the pet until you turn it off. The agent keeps working; the pet stops reacting. Petting it still gets a bounce. |
| **Right-click** | Opens the menu: quiet mode, the session list, skins, where the pet shows up, the project page, updates, quit. |
| **Stop** | `./bin/dsh-macos-pet --stop`, or stop `dsh web`. |

It starts in the background and detaches from your terminal, so you can close
the window you launched it from.

## States

<p align="center">
  <img src="docs/media/states.png" width="720" alt="idle, working, waiting, error, happy, sleeping">
</p>

Driven by your local DSH. Nothing to configure.

```mermaid
stateDiagram-v2
    [*] --> idle
    idle --> working: DSH starts a run
    working --> waiting: blocked on a confirmation
    waiting --> working: you answered
    working --> error: the run failed
    working --> happy: the run finished
    happy --> idle: settles back after 3.2s
    error --> working: the next run starts
    idle --> sleeping: agent and pointer both quiet for 90s
    sleeping --> idle: any activity, or a poke
```

| State | When |
| --- | --- |
| **idle** | Nothing to do — breathes, blinks now and then |
| **working** | DSH is running |
| **waiting** | Blocked on a confirmation, approval, or your input |
| **error** | The run failed |
| **happy** | A run just finished; settles back to idle after a few seconds |
| **sleeping** | Dozes when the agent is idle **and** your pointer has stopped moving. Any activity, or a poke, wakes it. |

## Skins

<p align="center">
  <img src="docs/media/skins.png" width="600" alt="The built-in skins">
</p>

Pick one from the Skin submenu, or start on it with `--skin <id>`. Every skin
has all six states at three frames each.

| Skin | In motion |
|---|---|
| **DeepSeek Whale (default)** | <img src="docs/media/loop-deepseek.gif" width="120" alt="deepseek"> |
| **Blue Whale** | <img src="docs/media/loop-bluewhale.gif" width="120" alt="bluewhale"> |
| **Threadcore** | <img src="docs/media/loop-threadcore.gif" width="120" alt="threadcore"> |
| **Nautilus** | <img src="docs/media/loop-nautilus.gif" width="120" alt="nautilus"> |
| **Jellyfish** | <img src="docs/media/loop-jellyfish.gif" width="120" alt="jellyfish"> |
| **Claude** | <img src="docs/media/loop-claude.gif" width="120" alt="claude"> |

> Idle is 2.4 seconds of stillness and then a very short blink. I tried giving the
> three frames equal time and it did not look right.

### Every skin, all six states

In order: idle · working · waiting · error · happy · sleeping

<p align="center">
  <img src="docs/media/skin-deepseek.png" width="820" alt="deepseek">
</p>
<p align="center"><sub>DeepSeek Whale (default)</sub></p>

<p align="center">
  <img src="docs/media/skin-bluewhale.png" width="820" alt="bluewhale">
</p>
<p align="center"><sub>Blue Whale</sub></p>

<p align="center">
  <img src="docs/media/skin-threadcore.png" width="820" alt="threadcore">
</p>
<p align="center"><sub>Threadcore</sub></p>

<p align="center">
  <img src="docs/media/skin-nautilus.png" width="820" alt="nautilus">
</p>
<p align="center"><sub>Nautilus</sub></p>

<p align="center">
  <img src="docs/media/skin-jellyfish.png" width="820" alt="jellyfish">
</p>
<p align="center"><sub>Jellyfish</sub></p>

<p align="center">
  <img src="docs/media/skin-claude.png" width="820" alt="claude">
</p>
<p align="center"><sub>Claude</sub></p>

**Make your own from a picture.** Hand an image to your agent and ask it to make
a desk pet skin. A skill ships with the plugin that turns one image into the
eighteen poses a skin needs — six states, three frames each. Your own image tool
does the generating, on your own credentials; nothing is sent anywhere by us.
Your skins live in `~/.dsh-macos-pet/skins/`, outside the package, so upgrading
the plugin does not delete them.

The skill stops twice on the way: once after the first pose, so you can throw it
away before paying for seventeen more, and once after the second, to check the
character survived being redrawn. If a run half-fails it tells you which poses
are missing and keeps the ones you already paid for.

**And then you can show it to someone.** One command draws the six states as a
single image:

```bash
./bin/dsh-macos-pet --skin-sheet <your-skin-id>
```

Submissions welcome in the [skin gallery](SKINS.md) — the preview image is the
whole contribution; your frames stay on your machine.

## Options

```bash
./bin/dsh-macos-pet --scale 0.5      # smaller (default 0.7)
./bin/dsh-macos-pet --skin jellyfish # start on a specific skin
./bin/dsh-macos-pet --reset          # forget saved position, size and skin
./bin/dsh-macos-pet --stop           # stop the running pet
./bin/dsh-macos-pet --foreground     # stay attached, log to this terminal
./bin/dsh-macos-pet --probe          # diagnostics, no window
./bin/dsh-macos-pet --inventory      # frames per skin per state
```

## Known limits

- There is a band of transparent margin around the pet, and clicks landing on it
  do not reach the window behind.
- A settings window and mini mode are not in this version.
- Nothing shows progress while a skin generates; your agent's own output is the
  only feedback during the eighteen images.

How it works inside, and where the first choice turned out wrong: [docs/INTERNALS.md](docs/INTERNALS.md).

What is planned next, and what is deliberately not: [docs/ROADMAP.md](docs/ROADMAP.md).

## License

MIT.
