dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DeepSeek Harness 的悬浮状态条:一行纯文字显示 CPU、内存、多 GPU 的温度与占用率,以及实时网速。零运行时依赖,数据仅在本机回环地址读取。
Author: DeepSeek + DeepSeek-Harness
中文 | English
A floating status capsule for the DeepSeek Harness web GUI: CPU, memory, every GPU and network throughput as one line of plain text.
⣿ CPU 9.6% 78.9°C 丨 MEM 43% 13.4/31.2 GB 丨 GPU 0% 51°C 0/11.9 GB 丨 网速 ↓ 1.3 MB/s ↑ 240 KB/s ⟳ ✕
(A layout sketch, not a screenshot. The real colors come from your DSH theme — see Theming and stacking.)
丨, then refresh and hide. No bars, gauges or sparklines; it is a
readout, not a chart. It wraps onto a second line rather than clipping a metric
on a narrow window.—, never a fabricated 0. A missing vendor tool degrades one metric, it never
blanks the capsule.| DeepSeek Harness | >= 0.1.5-rc.1, web or desktop profile |
| Node | >= 20 (the version Harness itself ships is fine) |
| Runtime dependencies | none |
Platform coverage for each reading:
| Reading | Windows | Linux | macOS |
|---|---|---|---|
| CPU utilization | ✅ os.cpus() | ✅ | ✅ |
| CPU temperature | ✅ ACPI thermal zones via typeperf | ✅ /sys/class/thermal + coretemp/k10temp | ❌ needs root (powermetrics) |
| Memory | ✅ | ✅ | ✅ |
| GPU utilization / temp / VRAM / power | ✅ NVIDIA via nvidia-smi; utilization-only fallback via Windows GPU performance counters | ✅ NVIDIA via nvidia-smi; AMD via amdgpu sysfs | ❌ |
| Network throughput | ✅ PDH network counters via typeperf | ✅ /proc/net/dev | ❌ |
Row by row: CPU usage, memory and network work everywhere the operating system publishes counters. GPU and CPU temperature need a source the operating system actually provides; where it does not exist, that one reading is blank and the rest keep working.
dsh plugin --profile web add github:GNX001/dsh-system-monitor
No build step is needed: the bundled browser half (lib/client.js) is committed,
so installing from git is enough. There is no npm release yet.
git clone https://github.com/GNX001/dsh-system-monitor.git
cd dsh-system-monitor
dsh plugin --profile web add link:$(pwd)
dsh plugin --profile web add dsh-system-monitor
Restart dsh web (or DSH Desktop) after installing. The tile appears in the
top-right corner, and Settings → System monitor tile gains a section for it.
DSH Desktop note. The desktop shell starts the Harness server itself, so a plugin install only takes effect on the next restart of DSH Desktop. If
dshis not on yourPATH, the same command works through the copy the desktop ships — for examplenode "$env:APPDATA\dsh-desktop\harness\profiles\node_modules\@deepseek-ai\dsh\lib\bin.js" plugin --profile web add github:GNX001/dsh-system-monitor.
| Action | How |
|---|---|
| Move the capsule | Drag anywhere on it except its buttons |
| Refresh immediately | The ⟳ button (forces a fresh hardware probe) |
| Hide the capsule | The ✕ button — reopen it in Settings → System monitor tile |
| Configure | Settings → System monitor tile |
Everything configurable lives in DSH's own Settings, not in a popover on the capsule:
| Setting | Default | Notes |
|---|---|---|
| Show the floating capsule | on | The way back after hiding it |
| Refresh interval | 1.5 s | 1 s – 10 s |
| Opacity | 94 % | 40 % – 100 % |
| CPU / CPU temperature / Memory / GPU | on | Item visibility |
| GPU temperature / VRAM / power draw | temp + VRAM on, power off | |
| Network speed (up and down) | on | Both directions always shown together |
| Compact mode | off | Tighter padding and type |
| Reset position / Restore defaults | — |
Colors. The capsule owns no palette. Every color is a DSH design token
(--dsw-alias-bg-layer-2, --dsw-alias-label-primary, --dsw-alias-state-warn-primary,
…), which the theme plugin declares on body for the light theme and on
body[data-ds-dark-theme] for the dark one. The capsule is a child of body, so
it inherits those declarations and the browser re-resolves them the moment the
theme changes — no listener, no re-render, and third-party themes such as
Catppuccin and neu-theme are followed because they rewrite the same tokens. Each
reference carries a literal fallback, so it still renders where the tokens are
absent.
Stacking. The capsule is fixed at z-index: 900. That has to sit between two
layers: conversation content (code blocks and tool cards use z-index: 1–12,
and because none of their ancestors creates a stacking context they paint over a
z-index: auto fixed element) and DSH's own menus and modals (1000+). So the
capsule covers a code block, and opening a menu still covers the capsule.
The tile's own options live in the browser. The host half is configured in
the profile's cordis.patch.yml, under the row this plugin inserts:
- insert:
- id: system-monitor
name: dsh-system-monitor
config:
# All optional; these are the defaults.
tickMs: 1000 # CPU/memory cadence (in-process, cheap)
gpuMs: 1500 # GPU probe cadence (spawns nvidia-smi)
cpuTemperatureMs: 5000 # CPU temperature cadence (spawns typeperf)
networkMs: 3000 # network throughput cadence (spawns typeperf)
cpuTemperatureScale: auto # auto | kelvin | decikelvin | decicelsius
gpu: true # false = never probe GPUs
cpuTemperature: true # false = never probe CPU temperature
network: true # false = never probe network throughput
networkExclude: [] # adapter name fragments to ignore (see below)
networkInclude: [] # fragments to count even if excluded
allowRefresh: true # false = reject ?refresh=1
nvidiaSmiPath: nvidia-smi # name or absolute path
enabled: true # false = mount nothing at all
networkExclude defaults to a list of loopback, VPN/overlay and virtual adapter
fragments (loopback, pseudo, virtual, isatap, teredo, vmware, wsl,
wireguard, tailscale, docker, and so on), plus any Windows duplicate
instance suffixed _2. That matters: a loopback or VPN adapter carries the same
packets as the physical one, so counting both doubles the reading — and a local
dev server alone can produce gigabytes of loopback traffic. The snapshot lists
every adapter with a counted flag, so you can see exactly what was included
before overriding anything.
cpuTemperatureScale exists because ACPI thermal zones are reported in
different units by different providers; auto detects the unit from the
magnitude, and the override is there for sources that disagree. See
How each number is obtained.
Both routes are GET, loopback-only, and Cache-Control: no-store.
| Route | Purpose |
|---|---|
/api/dsh-system-monitor/snapshot | The current reading (served from a cached sample; no IO in the request path) |
/api/dsh-system-monitor/snapshot?refresh=1 | Force a fresh hardware probe before answering |
/api/dsh-system-monitor/health | Plugin identity, version, cadence — never touches hardware |
snapshot returns:
{
"ok": true,
"version": "0.1.0",
"ts": 1780000000000,
"host": { "hostname": "dev-box", "platform": "win32", "arch": "x64", "uptimeSec": 3600, "pid": 42 },
"cpu": {
"usage": 23.4, // percent, null until a second sample exists
"perCore": [10, 90, 50, 0],
"cores": 4,
"model": "AMD Ryzen 7 8845HS w/ Radeon 780M Graphics",
"speedMHz": 3800,
"temperature": 81.9, // Celsius, null when unavailable
"temperatureSource": "acpi-thermal-zone",
"temperatureZones": [{ "name": "\\_SB.ECTZ", "celsius": 81.9 }]
},
"memory": { "totalBytes": 34359738368, "usedBytes": 20820942848, "freeBytes": 13538795520, "usage": 60.6 },
"gpus": [{
"index": 0,
"name": "NVIDIA GeForce RTX 5070 Ti Laptop GPU",
"vendor": "nvidia",
"source": "nvidia-smi",
"usage": 42,
"temperature": 61,
"powerWatts": 88.5,
"memory": { "usedBytes": 4294967296, "totalBytes": 12884901888, "usage": 33.3 }
}],
"gpuSource": "nvidia-smi",
"errors": [] // per-metric diagnostics, capped at 6
}
Every numeric field is null rather than 0 when the reading is unavailable.
127.0.0.1 / ::1
peer; anything else is 403 before a single metric is read. A request carrying
a proxy's X-Forwarded-For / Forwarded header is refused too, so a
reverse-proxied LAN client cannot reach the plugin by pretending to be local.The snapshot does reveal host inventory (CPU/GPU model names, memory pressure) to whatever can reach the loopback port. That is inherent to a system monitor; the fence is what keeps it local.
This plugin installs no drivers and reads no undocumented memory. Every reading
comes from one of three places, and each is labelled with its source field.
CPU utilization — os.cpus() cumulative per-core jiffies, differenced
between ticks. The first sample after start has no baseline, so usage is null
until the second.
Memory — os.totalmem() / os.freemem(). On Windows this is
GlobalMemoryStatusEx.ullAvailPhys (the "available" figure), which is why the
percentage matches Task Manager rather than a raw free-page count.
CPU temperature, Windows — the PDH counter set
\Thermal Zone Information(*)\Temperature, read with the built-in typeperf
tool. Windows exposes no CPU die temperature to a non-elevated process
(MSAcpi_ThermalZoneTemperature and the vendor WMI namespaces are refused or
absent without drivers), so the ACPI thermal zones are what is available.
On the unit. The PDH counter is widely documented as "degrees Kelvin", but ACPI's underlying
_TMPis deci-Kelvin — and deci-Kelvin is impossible for the values a real machine reports (355 deci-Kelvin is 35.5 K). Measured on a Windows 11 AMD laptop: the counter idles at ~355 and plateaus at ~367 under sustained 4-thread load. Read as Kelvin that is 82 °C → 94 °C, i.e. a laptop CPU settling just under its 95 °C throttle. Read as deci-Celsius the same trace would be a 1.2 °C rise under full load, which no physical package does. This plugin therefore reads Kelvin, andcpuTemperatureScalelets you override it for a source that behaves differently.
CPU temperature, Linux — /sys/class/thermal/thermal_zone*/temp and the
hwmon chips whose driver name is a CPU sensor (coretemp, k10temp,
zenpower, cpu_thermal, acpitz). The hottest zone is reported as the
headline figure and every zone is listed in temperatureZones.
GPU, NVIDIA (Windows and Linux) — nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total,temperature.gpu,power.draw,name --format=csv,noheader,nounits, invoked with a fixed argument vector. name is
requested last on purpose: GPU marketing names contain commas, and a trailing
free-text column can be re-joined safely whereas a middle one cannot.
GPU, Windows without nvidia-smi — the vendor-neutral PDH counters
\GPU Engine(*)\Utilization Percentage and \GPU Adapter Memory(*)\Dedicated Usage, grouped per adapter. Utilization is the maximum across an adapter's
engine instances, not the sum: the counters are per-engine percentages, so
summing a frame that is both rendered and copied would exceed 100%. This counter
set carries no temperature and no VRAM capacity, so those stay null.
GPU, AMD on Linux — amdgpu sysfs: gpu_busy_percent, mem_info_vram_used
/ mem_info_vram_total, and the hwmon temperature nodes. No process needed.
Network throughput, Windows — the PDH counters
\Network Interface(*)\Bytes Received/sec and \Bytes Sent/sec, read with the
same typeperf call pattern as the thermal zones. Those counters are already
rates, so no differencing is needed; the columns are counter-major (every
Received instance, then every Sent one), so the direction is read from each
header path rather than assumed from the column position. Only adapters passing
the exclusion filter are summed.
Network throughput, Linux — /proc/net/dev, which carries cumulative byte
counts. This is the one reading that is differenced rather than used directly, so
the first sample after start has no interval to measure and reports —, exactly
like CPU utilization.
Throughput is never a temperature-style alarm — it has no meaningful threshold, so the value never turns amber or red.
Zero is treated as "no reading" for every temperature path: ACPI reports 0
for an absent sensor and kernel drivers publish 0 for an unpopulated one.
Reporting that as a genuine 0 °C would be worse than reporting nothing.
The tile never appears.
The browser half needs the host half. Check GET /api/dsh-system-monitor/health
in the same browser: a JSON reply means the host half is up and the problem is
client-side (reload the app). A 404 means the plugin did not load — confirm the
row is in the profile's cordis.patch.yml and restart dsh web.
GPU shows No GPU detected.
Nothing answered. Verify nvidia-smi is on PATH
(nvidia-smi --query-gpu=name --format=csv,noheader); if it lives elsewhere, set
nvidiaSmiPath to the absolute path. On Windows the vendor-neutral fallback
still reports utilization for AMD/Intel adapters, but without temperature or a
VRAM total — that data is simply not published by the platform.
CPU temperature shows —.
On Windows the ACPI thermal-zone counter is absent on some virtual machines and
some older firmware. On Linux no CPU hwmon chip was found. On macOS there is
no non-root source at all. Check the errors array in the snapshot for the
specific reason.
The temperature looks wrong by a fixed factor.
Set cpuTemperatureScale — see the unit discussion above.
网速 / NET shows — or stays at 0 B/s.
0 B/s means the counters were read and the machine is simply idle, which is
correct. — means nothing could be measured: macOS has no non-root source
(unsupported), or on Linux the first sample had no interval yet — the next poll
resolves it. To see what was counted, read network.interfaces in the snapshot:
each entry carries a counted flag. If your traffic runs over an adapter the
default filter drops (a VPN, or a virtual switch), add a fragment to
networkInclude.
The capsule responds slowly, or a machine feels busier.
Each of gpuMs, cpuTemperatureMs and networkMs controls one short-lived
helper process. Raise the cadence, or set gpu: false / cpuTemperature: false
/ network: false to switch a probe off entirely. CPU, memory and the CPU rate
counters cost nothing measurable.
npm install --ignore-scripts # --ignore-scripts avoids esbuild's postinstall probe
npm run build # bundles src/client/** into lib/client.js
npm test # node --test test/
npm run verify # build, then test
npm run test:inline # the same suites in one process
npm run dev # host half on :43199 + a page that boots the real tile
npm test needs the test runner to spawn a child process per file. Sandboxes and
restricted environments can refuse that; npm run test:inline imports the same
suites into a single process instead.
npm run dev mounts the real host half on http://127.0.0.1:43199/ and serves a
page that boots the real lib/client.js with the same __ModuleLoader__
contract the shell uses — so the tile can be exercised against actual hardware
without installing into a DSH profile and restarting the app. The page inlines
the shell's own :root / body / body[data-ds-dark-theme] rule blocks
(extracted from @deepseek-ai/dsh-client-ui-theme, overridable with
DSM_THEME_CLIENT) and offers a theme toggle, so the token-following behaviour
can be checked for real rather than taken on trust. It also prints the tile's
rows as text on startup, which is the quickest way to read the layout without a
browser. It is a development tool and is not part of the published package.
The host half (lib/index.js and lib/metrics/**) is plain ESM and needs no
build. Only the browser half is bundled, because the DSH client module system
wants a single classic script that calls
window.__ModuleLoader__.load({ id, factory }); the build wraps esbuild's cjs
output in exactly that factory and keeps react / react-dom/client external to
the shell's module table.
| Path | What it is |
|---|---|
lib/index.js | Host half — the cordis plugin entry |
lib/routes.js, lib/http.js, lib/trust.js | Routes, JSON writer, loopback fence |
lib/metrics/ | Collectors: parse, exec, cpu, memory, cputemp, gpu, monitor |
src/client/ | Browser half sources: model (pure logic), tile, settings, styles, locales |
lib/client.js | Built browser half (committed so a link: install needs no build) |
test/ | node:test suites, including a jsdom run of the built bundle |
tools/ | Development only: dev-server.mjs, run-tests.mjs |
test/client-bundle.test.mjs loads the built lib/client.js in jsdom
through the real __ModuleLoader__ contract, with a require shim that only
answers the modules the shell actually provides — so a bad external (or a stale
bundle) fails the suite rather than the app. test/fixtures/ holds real
typeperf and nvidia-smi output captured from a hybrid-GPU Windows laptop, so
the parsers are tested against what the tools actually print, not idealized input.
Run npm run build before npm test after touching anything under src/client/.
The Unlicense — completely free, with no conditions on use.
This software is released into the public domain: anyone may copy, modify, publish, use, compile, sell or distribute it, in source or binary form, for any purpose, commercial or non-commercial, with no attribution, no notice to keep and no terms to comply with.
See LICENSE for the full text, or https://unlicense.org. As with any public-domain release, the software comes as-is, with no warranty.