Back to home@mjw-git

dsh-pet

Desktop pet plugin for DeepSeek Harness (dsh) — grows with your agent token usage; XP economy, custom spritesheets, SSE status push, one-command install

Stars
0
Language
TypeScript
Created
Aug 24, 2026
Updated
Aug 24, 2026
GitHub repo

Introduction

dsh-pet — a standalone desktop-pet plugin for DeepSeek Harness

English | 中文

An independent desktop-pet plugin for DeepSeek Harness (dsh, a Cordis-based agent shell). The pet lives in the corner of the Web UI and grows alongside your agent: every assistant/message sums the four token buckets (input/cacheRead/cacheWrite/output) into XP, levels unlock a title ladder, and the agent's live status (thinking / calling-tool / tool-error / answered …) drives the pet's current animation via a millisecond-latency SSE push. Name the pet, swap its spritesheet, scale and drag it anywhere; the hover panel is minimalist monochrome.

The pet floating over the DeepSeek Web UI, growing with token usage

The full local dev→deploy loop (two-repo sync, packing, profile install, verification, troubleshooting) lives in DEVELOPMENT.md.

Features

  • Grows with token usage — consumption is XP: level, title ladder (a monochrome brightness ramp encodes prestige), and an XP progress bar
  • Live status sync — the host projects session events onto coarse statuses (thinking / calling-tool / tool-ok / tool-error / answered / turn-end) and pushes them over the /pet/events SSE stream, so the pet switches animations in step with the page's streaming output (a 3s poll remains as fallback)
  • Status → action mapping — pick which spritesheet row plays for each status; saved locally
  • Custom spritesheets — upload a single image plus a JSON layout config; the host validates cell/frame geometry and rebuilds the manifest
  • Rename / resize / drag — pet name, size (50%–200%), and position all persist locally
  • Consumption cannot be wiped — tokens/XP/level are recomputed from durable session logs on every boot (ground truth); /pet/reset accepts the sprite scope only
  • Minimalist monochrome UI — pure black/white card, serif masthead, mono micro-labels, zero radius, no shadows

Install (one command)

dsh plugin --profile default add \
  @dsh-pet/bundle @dsh-pet/host @dsh-pet/client
# then run the shell, e.g.
dsh web --profile default --apiKey <key>

@dsh-pet/bundle is the installer layer: its cordis.patch.yml inserts the pet / ui-pet rows into the composed tree, and installing it activates the layer automatically (appended to dsh.profile.bundles) — no hand-edited yml. After install the host registers the /pet/* routes plus the SSE stream, and the browser half is auto-discovered by dsh-client-modules (via the dsh.client declaration) and served at /plugins/@dsh-pet/client/client.js. Removal is one … remove … command.

All three packages must go in one command: the loader resolves bare package names in patch rows through the profile directory's top-level Node resolution, so both plugin packages must be direct profile dependencies (the bundle's peerDependencies only declare the relationship).

Repository layout

This repo is decoupled from the upstream monorepo: it never depends on the deepseek-harness source tree, only on its published npm packages (pinned exact to the 0.1.0-rc.7 line; framework on @deepseek-ai/cordis@4.0.1). Upstream updates never change this repo's behavior until you re-pin.

packagepathrole
@dsh-pet/hostpackages/pet/dsh-pet/Host: economy, storage domain, spritesheet validation, /pet/* routes, /pet/events SSE
@dsh-pet/clientpackages/client/ui-pet/Browser: the bottom-right desktop-pet overlay (a dsh.client client module)
@dsh-pet/bundlepackages/bundle/pet/Installer: the one-command composition patch
dsh-pet/
├─ package.json / pnpm-workspace.yaml / vitest.config.ts / tsconfig.base*.json
├─ config/tsdown-client.ts        # standalone client-bundle renderer (module-loader closure, CSS inline)
└─ packages/
   ├─ pet/dsh-pet/                # host: service, domain, economy, spritesheet, /pet/* routes
   │  ├─ src/{index,spec,types,economy,catalog,usage,default-sprite,spritesheet,invariant}.ts
   │  └─ tests/{catalog,economy,usage,default-sprite,spritesheet}.spec.ts
   ├─ client/ui-pet/              # browser: the pet overlay; consumes @dsh-pet/host types
   │  ├─ src/client/{PetCompanion,PetCompanion.module.css,agent-status,titles,locales,pet-api,slots,spritesheet,index}
   │  └─ tests/spritesheet.client.spec.ts
   └─ bundle/pet/                 # installer: package.json + cordis.patch.yml (no code)

Commands

pnpm install        # resolves all @deepseek-ai/* deps from npm (pinned)
pnpm build          # tsc host → lib/types · tsc client · tsdown client bundle → lib/client.js
pnpm typecheck      # tsc --noEmit, both packages
pnpm test           # vitest: host economy/usage/spritesheet/catalog + jsdom pet spec (85 tests)

How the client half is served

The browser half is a standalone client module (dsh.client declaration + lib/client.js). At runtime dsh-client-modules serves it on demand at /plugins/<package-name>/client.js; the SPA lazy-fetches it, so installing a new client package does not require recompiling the web bundle. Published client packages ship only lib/; ./src/* exports are source-launch placeholders and are not loadable from a published tarball — deep …/src/… imports are removed here.

Publishing (maintainer-run — needs npm credentials)

All three packages publish to the @dsh-pet scope. Use pnpm publish (not npm publish): pnpm normalizes the intra-workspace workspace: specs (client → host, and the bundle's peers) to real versions.

# host first, then the browser half, then the bundle (its peers point at the first two)
pnpm -C packages/pet/dsh-pet publish
pnpm -C packages/client/ui-pet publish
pnpm -C packages/bundle/pet publish

Once on the registry, the local install command switches from file: tarballs to bare package names (no pnpm file:-cache quirks). Keep the three packages' versions in lockstep.

Live boot

The end-to-end run (dsh web → the floating pet appears bottom-right → send a message → XP grows and titles change → the pet switches animations in step with the streaming output → upload a spritesheet / rename / resize) requires a running harness and an API key. Everything short of the live provider call is exercised by the test suite; the full local-tarball→profile install chain has been verified live (see the curl checklist in DEVELOPMENT.md §3.7).

Version policy

Dependencies are pinned in each package's package.json. To absorb a harness release: bump the @deepseek-ai/dsh-* pins and the framework lines together, re-run pnpm install && pnpm build && pnpm test, and re-verify that the client bundle's external platform-module set (in config/tsdown-client.ts) still matches what the new harness serves.