dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DeepSeek Harness mascot plugin - a clickable character sprite (Arknights' Closure / BanG Dream!'s Sengoku Yuno) showing token cache hits, context pressure and account balance.
English | 中文
A mascot for the DeepSeek Harness (DSH) Web GUI. It sits in the bottom-right corner. Click it and a panel opens with the session's token cost and cache performance.
The image above is lib/client.js running in Chromium with the built-in placeholder, i.e. a fresh clone before npm run fetch-art. With the official artwork installed it looks like docs/preview-official.png, which is generated locally and not committed.
DSH Desktop loads plugins through the profile's cordis.patch.yml. cordis.yml is rewritten to [] on every boot; do not edit it.
The plugin has no runtime dependencies. Two ways in, pick one.
Option A: point at the file directly.
# ~/.dsh/profiles/desktop/cordis.patch.yml
- insert:
- id: mascot
name: 'file:///C:/Users/<you>/Desktop/kexier/dsh-plugin-mascot/lib/index.js'The file: URL names a file, not the folder. A relative path also works and resolves against the patch file's directory.
Option B: install it as a profile dependency.
dsh plugin --profile desktop add "/absolute/path/to/dsh-plugin-mascot"
dsh plugin --profile desktop add link:/absolute/path/to/dsh-plugin-mascot
dsh plugin --profile desktop add "github:ZYAONS/dsh-plugin-mascot"- insert:
- id: mascot
name: dsh-plugin-mascotAdd config to either form:
- insert:
- id: mascot
name: dsh-plugin-mascot
config:
baseUrl: 'https://api.deepseek.com'
cacheTtlMs: 30000Restart DSH Desktop. The desktop composes its patch layers once per launch and does not watch the profile directory. Reloading the page is not enough. After the first mount, edits to lib/client.js hot-reload without another restart.
Configure the plugin in a browser before installing anything:
https://zyaons.github.io/dsh-plugin-mascot/
Seven sections:
cordis.patch.yml to copy or downloadThe page is static, runs on GitHub Pages, and reads nothing from your machine. State lives in localStorage. ?theme= and ?character= deep-link both, and ?theme= also selects the character.
Served by the plugin at /dsh-mascot/console/ it also reads the artwork on your own machine.
10 page themes: a neutral one plus one per character.
auto: silver, square cornersPicking a character's theme selects that character, and picking a character selects its theme. The neutral theme belongs to no character; it is the only one that can disagree with the artwork, and it hides the music row.
The console is English. The panel is Chinese. art/looks.json carries nameEn / roleEn / taglineEn beside name / role / tagline, and build-site.mjs copies the English set into the catalogue. check-site fails if a rendered record has no translation, or if Chinese reappears outside the rights notice.
All artwork belongs to Hypergryph or Bushiroad. The repository ships the declaration, not the images.
npm run fetch-art # download, cut out where needed, re-index
npm run fetch-art -- --force # re-download regardless of hashNine characters, 22 looks:
Seven are *Arknights*; Yuno and Sakiko are *BanG Dream!*. Sakiko and Makoto are collab designs. art/looks.json is hand-written and art/index.json is generated; every entry carries source URLs and a sha256, checked on download.
To add a look, add an entry to looks in art/looks.json and run npm run fetch-art. No code changes.
scripts/art-sync.mjs measures each image's alpha bounding box in a browser, derives the framing for both seats (104×172 sprite, 38×50 portrait), and samples an accent colour. The results go into art/index.json, which the host half filters to what is on disk and serves to the browser.
npm run art:watch re-indexes whenever a file lands in art/.
A missing file falls back to a neutral placeholder: a dashed box with a generic glyph. It depicts no character.
Three layers. The first two are motion; the third is the browser playing a file.
A 2D bone and skinned-mesh renderer in WebGL2.
art-sync measures each image's silhouette profile as 32 horizontal bands. The client finds the neck from the band where the silhouette pinches.root at the feet, spine at the hip, neck at the neck, armL/armR at the shoulders, foreL/foreR at the elbows. Children inherit parent transforms.UNPACK_PREMULTIPLY_ALPHA_WEBGL.If WebGL2 is unavailable, a shader fails to compile, or the image taints the canvas, it renders a plain `. The rig can only add motion. The preview script prints rig: ON (canvas 140x232)` to report which path ran.
The idle, greeting and walk come from the games' own Spine models, mirrored by Ark-Models and read with the official 3.8 runtime.
npm run measure:motion # all six animations
npm run measure:motion -- --model=4228_closur --character=closure
npm run measure:motion -- --model=4228_closur --movesArknights chibi have six animations: Default, Interact, Move, Relax, Sit, Sleep. All six are measured for all 8 characters that have a model; art/motion.json is keyed by character. Three are used:
Relax as the idle: waist 0.4°, forearm 28.6°Interact as the greeting: waist 17.6°, forearm 79.5°Move as the walk-in on a look change: IK feet ±89 unitsSit and Sleep carry almost no motion in either channel. Sleep moves three bones by at most 2.2 units, Sit's largest translation is an eyeball, and Default is a zero-length animation. They are measured and not inlined.
npm run motion:literal # regenerate the inlined block in lib/client.jsThe generated block ships idle, greet, move and the blink per character. A character with no entry falls back to the default.
A character that declares gesture: "evoker" raises its right arm to its temple on click: the upper arm turns 140°, the forearm folds 120°.
Yuno's Q-version is two drawn poses in one sheet. cutout.mjs splits them, crops each to its own bounding box, and gives both one shared scale. The browser cross-fades every 5.2 s. It stands down when the bone rig is on.
Any frame that is a GIF or animated WebP plays on its own.
The panel plays the character's Siren Records album through a hotlinked official CDN. art/siren.json maps a character to the album for the event they came from; art/songs.json covers characters with a 30-second Apple Music preview and no Siren release.
npm run siren:sync # re-resolve albums and previews
npm run siren:sync -- --listNo audio file is stored. The host serves both maps through /api/looks.
Each character's dock has a backdrop: a room the plugin draws from gradients, or official furniture art.
npm run fetch-rooms # download what is missing
npm run fetch-rooms -- --list # show the declaration
npm run fetch-rooms -- --force # re-downloadImages go to art/room-.. The host finds them by filename, so a restart is all that is needed. art/rooms.json declares which set each one is and where it came from. Deleting art/room-* returns the character to the drawn room.
刷新 re-reads the balance. Click the backdrop or press Esc to close the panel.The choice is stored in localStorage. With the system's reduce-motion preference set, all animation stops.
The pill under the sprite shows three figures. 余额 ¥128.42 is the account balance, fetched host-side; when the lookup fails it reads 点我 Token / 余额 instead. 命中 89.0% is this session's cache hit rate. 今天 1.2M is the tokens spent today on this machine.
The daily figure needs the host to keep books: a session that ended an hour ago takes its numbers with it. The host keeps .cache/usage-daily.json, keyed by session, and stores a high-water mark per session. The browser reports a cumulative total repeatedly, so re-reporting adds nothing; a total that goes backwards never subtracts. Midnight resets it.
Every field has a default.
Field · Default · Meaning routePrefix · /dsh-mascot · path prefix claimed on the host webserver baseUrl · https://api.deepseek.com · provider origin hosting the balance endpoint apiKeyRef · DEEPSEEK_API_KEY · credentials reference in ~/.dsh/.credentials.yaml cacheTtlMs · 60000 · freshness of a balance answer; failures are never cached timeoutMs · 10000 · upstream request budget allowForeignBaseUrl · false · allow the API key to go to a baseUrl that is not DeepSeek's own host artDir · the plugin's own art/ · directory holding the artwork
Behind a proxy or self-hosted gateway, point baseUrl at it and set `allowForeignBaseUrl: true`. Without that flag the plugin refuses to send the key to any other host, and says which host it refused. The gateway must implement GET /user/balance.
dsh-plugin-mascot/
├── lib/
│ ├── index.js host half: the /dsh-mascot routes
│ └── client.js browser half: sprite, panel, themes, renderer, music
├── art/
│ ├── looks.json characters, themes, urls, hashes
│ ├── index.json generated by art-sync
│ ├── motion.json generated by measure-motion, keyed by character
│ ├── eyes.json blink boxes and the blinks' ramp
│ ├── rooms.json backdrop declarations
│ ├── siren.json character to Siren Records album
│ └── songs.json character to official preview
└── scripts/
├── fetch-art.mjs download, cut out, hand off to art-sync
├── cutout.mjs key out a background; split a multi-pose sheet
├── art-sync.mjs measure boxes and accents, write index.json; --watch
├── measure-motion.mjs read a Spine model's animations
├── motion-literal.mjs inline the measured curves into lib/client.js
├── siren-sync.mjs resolve albums and previews
├── fetch-rooms.mjs download backdrops
├── chrome.mjs Chromium discovery, shared by the browser scripts
├── check.mjs the plugin's self-test
├── check-site.mjs drives the console over CDP
├── build-site.mjs generate the console's catalogue and rig
├── preview.mjs render docs/preview*.png from the real code
└── verify-profile.mjs pre-flight a live DSH profileThe DSH GUI is a slot registry. This plugin registers one entry: