dsh-user-profile
Manifest validEdit your own display name and avatar in DeepSeek Harness — shown in the sidebar account chip. | DSH 插件:自定义用户名与头像。
dsh-user-profile
Your own name, avatar and brand, inside DeepSeek Harness.
Set a username and avatar in Settings → Profile, or replace the product name and logo in Settings → Brand; both show up immediately in the sidebar.
A local display profile for DeepSeek Harness.
中文 · English
Why
The account area at the bottom-left of DSH renders the DeepSeek Platform account — read-only — and degrades to a generic "More" button when nobody is signed in. The top-left corner is a fixed product wordmark. Neither is yours to change.
dsh-user-profile fills both gaps: a name and avatar stored locally and overlaid on the account control, and an optional product name and logo that replace the sidebar brand. The account menu (settings / feedback / sign out) keeps working; only the face changes.
| 🪪 Username | Up to 32 characters, any script; leave it empty for the default |
| 🖼️ Avatar | PNG / JPG / WebP, center-cropped and downscaled to 192×192 |
| 🏷️ Brand | Product name (≤ 32 characters) and a logo for the top-left corner |
| 👀 Live preview | See the result while editing; the sidebar updates on save |
| 🔄 Reversible | Reset clears the profile and the brand restores the shipped wordmark |
| 🌐 Localized | Follows the DSH language setting (中文 / English) |
| 💾 Local only | Nothing uploaded or synced; it lives under your own ~/.dsh |
Install
As a bundle (recommended)
The package declares dsh.bundle.patch, so the official CLI also mounts the plugin row:
dsh plugin --profile web add github:xbbkok/dsh-user-profile
Use desktop instead of web on the desktop app, then reopen DSH.
From a local checkout
Append an insert entry to the profile's cordis.patch.yml. The loader uses import(), so the specifier must be a file:// URL — a bare Windows path fails silently:
- insert:
- id: dsh-user-profile
name: 'file:///C:/path/to/dsh-user-profile/lib/index.js'
Profile patch locations:
| Build | Path |
|---|---|
| Desktop | %USERPROFILE%\.dsh\profiles\desktop\cordis.patch.yml |
| Web | %USERPROFILE%\.dsh\profiles\web\cordis.patch.yml |
The row is keyed by id, so a later bundle install overrides the same row instead of mounting a second copy.
Usage
- Sidebar account chip → Settings (
Ctrl+Alt+,). - Profile: set a name, pick an image, press Save.
- Brand: type a product name, upload a logo, press Save.
- Reset in either page clears that piece and restores the shipped look.
Storage
%USERPROFILE%\.dsh\plugins-data\dsh-user-profile\profile.json
{
"version": 2,
"name": "Ada",
"avatar": "data:image/png;base64,…",
"brand": { "name": "ACME Studio", "icon": "data:image/png;base64,…" },
"updatedAt": "2026-01-01T00:00:00.000Z"
}
heartbeat.txt in the same directory is rewritten on every host mount, so "did it load?" is a fact you can check.
The avatar is a 192×192 PNG data URL (usually 10–40 KB); the brand logo is 96×96. Both are cropped and compressed in the browser — the original file never touches disk.
API
The host half registers two methods on the DSH web server (same origin as the GUI):
| Method | Path | Notes |
|---|---|---|
GET | /user-profile | { ok, profile, storagePath, capabilities } |
POST | /user-profile | Body { name?, avatar?, brand?: { name?, icon? } }; omitted fields keep their value, null clears the image |
Validation: names are limited to 32 code points with control characters stripped; avatars and logos accept only PNG/JPEG/WebP data: URLs (SVG is rejected — it is executable markup, not a picture) and a byte cap; writes carrying an Origin that does not match the local host get a 403, so another web page cannot rewrite the profile cross-site.
capabilities.brand tells the client that this host half understands brand; a page served by an older host half shows a "restart DSH" note instead of losing an edit.
How it works
One package, two halves, like the other DSH UI plugins:
lib/index.js(host / Node) — owns/user-profile: validation, atomic write (temp file + rename), heartbeat. No third-party runtime dependencies.lib/client.js(browser) — registers two settings sections throughctx.slots.register({ name: 'settings.section', … }), claims the brand slots, and overlays the avatar and name on the account triggerbutton[data-signed-out].
Deliberate choices:
- The brand uses the official slots.
sidebar.brand.markandsidebar.brand.nameare single slots: registrations at different priorities coexist and the lowest priority renders. The plugin claims both at priority-1and renders the shippedFishLogo/BrandWordmarkwhenever nothing custom is set, so an unconfigured install is pixel-identical to stock and clearing the brand restores it with no teardown. - No forking of someone else's component. The account button belongs to
dsh-client-ui-settings-account; editing that package would be fragile and would be overwritten by updates. The bundle decorates the DOM instead: it records each native child'sdisplay, hides it, inserts its own avatar and label, and restores everything when the profile is cleared. AMutationObserverre-applies after React re-renders, and every write is idempotent so the observer cannot feed itself (covered by a test). - Failures stay inside. A missing web server, an unwritable directory or a failing route registration produce a warning, never a broken plugin tree.
- Language follows the app. Dictionaries go through
ctx.locale.register('user-profile', { zh, en })and are read throughctx.locale.bind(), so the page honours DSH's own language setting. - No build step.
lib/*.jsis readable ESM; thewindow.__ModuleLoader__.load()factory shape is consumed directly by the client module system — no bundler, nopostinstall.
Compatibility
- DSH 0.2.x (
@deepseek-ai/dsh >= 0.1.2-rc.1), desktop and web builds. - Uses the
settings.sectionslot, thesidebar.brand.mark/sidebar.brand.nameslots (priority shadowing), and thedata-signed-outattribute of the account trigger; all are stable since 0.1.x. - Display only: sign-in, billing and model calls are untouched.
Known limitations
- It changes the displayed profile and brand, not the DeepSeek Platform account.
- The profile is per machine; it does not sync across devices.
- The brand replaces the expanded wordmark; the collapsed rail keeps showing the mark slot, which is where a custom logo also appears.
- If a future DSH build renames
button[data-signed-out], the account-chip overlay silently stops applying (the settings pages keep working). - The account-chip overlay is DOM decoration on a component owned by another package, not an official slot.
Development
npm install # jsdom only, for running the browser half without a GUI
npm test
27 checks: host validation, atomic persistence, the HTTP handler (GET / POST / brand merge and reset / malformed JSON / over-long name / SVG disguise / cross-origin write / wrong method), the 1.0.0 document upgrade, bundle loading, slot registrations with localized labels, brand slots at priority -1 with shipped fallbacks, chip overlay and exact restoration, collapsed rail, observer settling.
Editing lib/client.js hot-swaps in the page; editing lib/index.js needs a DSH restart to re-import.
Uninstall
Remove the dsh-user-profile row from the profile's cordis.patch.yml (or drop it from dsh.profile.bundles), delete %USERPROFILE%\.dsh\plugins-data\dsh-user-profile\, and refresh.
License
MIT © 2026 xbbkok
Comments
Loading…