← Back to home@DoctorxPriestess

dsh-status-indicator

A Windows system-tray indicator that monitors and controls a local DeepSeek Harness (DSH) web instance — the process started by dsh web

Stars
0
Language
PowerShell
Created
Sep 12, 2026
Updated
Sep 12, 2026

Introduction

dsh-status-indicator

A Windows system-tray indicator that monitors and controls a local DeepSeek Harness (DSH) web instance — the process started by dsh web (node .../@deepseek-ai/dsh/lib/bin.js web).

The tray icon is the whole UI: green = running, yellow = starting/stopping, red = stopped/error. Right-click for start / restart / stop / open the Web UI.

中文文档见 README.zh-CN.md。


Features

Live statusTray icon colour + tooltip (PID, port, uptime, memory); balloon on state change
Lifecycle controlStart / Start as administrator (UAC) / Restart / Stop
Open the Web UIOpens the browser at the authenticated URL (see below)
Self-elevation"Restart indicator as administrator" hands the single-instance mutex over to an elevated copy
Three entry points-Tray, -Once (one-shot status report), or an interactive console menu
Configurable loggingLevel / directory / rotation via status-indicator.config.json
AutostartReady-to-use scheduled-task recipe (logon trigger, highest privileges)

Requirements

  • Windows (uses the WinForms tray, CIM and Get-NetTCPConnection)
  • Windows PowerShell 5.1 or PowerShell 7+ — the script is UTF-8 with BOM
  • Node.js on PATH
  • DeepSeek Harness installed, i.e. a directory containing node_modules\@deepseek-ai\dsh\lib\bin.js

Quick start

# one-shot status report
powershell -NoProfile -ExecutionPolicy Bypass -File .\status-indicator.ps1 -Once

# tray mode
powershell -NoProfile -ExecutionPolicy Bypass -File .\status-indicator.ps1 -Tray

# interactive console menu
powershell -NoProfile -ExecutionPolicy Bypass -File .\status-indicator.ps1

Parameters

ParameterDefaultMeaning
-Port3000Port the indicator uses when it starts DSH
-DshDirauto-detectedDirectory containing node_modules\@deepseek-ai\dsh
-OncePrint status once and exit
-TrayTray mode

-DshDir is auto-detected in this order:

  1. the -DshDir argument
  2. $env:DSH_INSTALL_DIR, then $env:DSH_DIR
  3. the directory of the dsh command (e.g. <install>\dsh.cmd) and its parents
  4. %APPDATA%\npm\node_modules (global npm installs)

If none match, the indicator reports a clear error instead of guessing.

Port note. -Port only affects instances the indicator starts itself. If your dsh web is configured to listen elsewhere, pass the matching -Port, otherwise the indicator will launch its own instance on 3000.

How it works

UI (tray / console) ──request queue──▶ Worker (single long-lived engine) ──▶ Core (detection / process control)
        ▲                                                                          │
        └──────────────────────── StateStore (snapshots) ◀─────────────────────────┘
  • The UI thread never blocks. Tray clicks and the 1-second timer only enqueue requests and read a snapshot. All CIM queries, TCP probes, process starts/stops and Start-Process calls run on a background worker engine, so the context menu is always instantly responsive.
  • Lifecycle operations are serialized through one queue, each tagged with an operationId that appears in the log.
  • Nothing is assumed to have succeeded. "Spawned a process" ≠ "started successfully": a start is only reported as successful after the process is observed and the port is listening. A stop is only successful once the PID is confirmed gone.
  • Port ownership is decided by joining PID → command line → listening port. A port held by an unrelated program is never treated as "running"; it is only reported as a diagnostic, and only the port the indicator is itself binding can fail a start early.
  • HTTP 401/403 does not mean "stopped". A successful TCP connect counts as "listening".
  • Single instance via a named mutex (Local\DshStatusIndicatorTray) — there is no PID file. When elevating, the new copy waits up to 30 s for the old one.
  • Bounded cleanup. Timer, NotifyIcon, menu, native HICONs, mutex and worker are all released on exit, and exiting never kills a running Harness.

Authenticated Web UI URL

dsh web mints a per-process random launch token that lives only in memory and is printed exactly once at startup:

dsh web: http://127.0.0.1:3080/?token=<43-char-base64url>

Visiting that URL mints a signed HttpOnly cookie (30 days by default, bound to the host:port authority). Any request without the token or cookie gets 401 dsh web authentication required.

Because the token is never written to disk, the indicator redirects the launched process's stdout to a capture file and parses the URL from it:

  • capture files are named .dsh-web-url-*.txt in the project root, at most 5 are kept, and they are deleted when the instance stops;
  • the token is held in memory only and is never written to the log;
  • .dsh-web-url-*.txt is gitignored — never commit it;
  • if no token URL could be captured, the indicator falls back to the plain URL (which still works if the browser already holds the cookie).

Configuration

status-indicator.config.json (created with defaults on first run; changes take effect on the next start, there is no hot reload):

{
  "logging": {
    "enabled": true,      // false = no I/O and no thread at all
    "level": "INFO",      // DEBUG / INFO / WARN / ERROR
    "directory": "logs",  // relative to the project root; absolute paths are rejected
    "maxFileSizeMB": 5,   // 1-100
    "maxFiles": 5         // 1-50
  }
}

Invalid values fall back to defaults and are recorded — a broken config never prevents startup. Logging is a low-overhead side channel: an in-memory queue plus a single writer thread, so the UI thread never touches the disk, with a bounded flush on exit.

Autostart (logon, elevated)

Run once from an elevated PowerShell:

schtasks /Create /F /TN "dsh-status-indicator" /SC ONLOGON /RL HIGHEST /DELAY 0000:05 `
  /TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File \"C:\path\to\status-indicator.ps1\" -Tray -Port 3000"

/RL HIGHEST runs the tray with administrator rights, so starting DSH does not raise a UAC prompt. Remove it with schtasks /Delete /F /TN "dsh-status-indicator".

Testing

All suites are isolated: they never touch a real, already-running DSH instance.

powershell -NoProfile -ExecutionPolicy Bypass -File .\test\run-lifecycle-tests.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\test\run-logging-tests.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\test\run-token-url-tests.ps1
SuiteCovers
run-lifecycle-tests.ps1Start/verify, stop/verify, restart, start failure → ERROR, port-conflict diagnostics. Uses a fake Harness (test\dsh\...\bin.js) and a process filter restricted to that fake, so real processes are never matched.
run-logging-tests.ps1Logging on/off, invalid-JSON fallback, bad level, absolute-path rejection, rotation, resume.
run-token-url-tests.ps1Launches a real DSH on a spare port (-TestPort, default 3999), asserts the token URL is captured, and verifies bare URL → 401 / token URL → 303. Cleanup only kills PIDs on that test port.

Expected results: 14/14, 12/12, 9/9.

Troubleshooting

SymptomCause / fix
dsh web authentication required in the browserThe plain URL was opened. Let the indicator start/restart the instance so it can capture the token, or open the URL printed by dsh web once to set the cookie.
DSH directory not foundAuto-detection failed. Pass -DshDir, or set DSH_INSTALL_DIR.
Tray icon does not appearAnother copy holds the single-instance mutex — check the log; a duplicate copy exits after 30 s.
Status stays red although DSH is runningDSH listens on a port outside the candidate set (-Port, 3000, 3080). Start the indicator with the matching -Port.
Chinese text renders as garbageThe script must stay UTF-8 with BOM; re-save with a BOM after editing.

License

MIT