English · 简体中文
Diagnose and repair a local DeepSeek Harness (dsh) installation: probe → AI diagnosis → transactional repair.
Zero runtime dependencies (Node built-ins only, Node ≥ 22.19). That matters: it still runs when dsh itself is broken. That is the whole reason this is a standalone tool rather than a dsh plugin — a plugin goes down together with the thing it is supposed to fix.
machine probe (deterministic, costs nothing)
↓
AI diagnosis (DeepSeek official API, reads the report only)
↓
repair plan (model may only pick from an action registry — it cannot make you run a command)
↓
transactional execution (snapshot → journal → apply → generate a one-click rollback script)
~/.dsh/dsh-fix/
├── src/
│ ├── core/
│ │ ├── paths.mjs path resolution (DSH_HOME / DSH_FIX_HOME overrides)
│ │ ├── util.mjs file/process/string helpers, secret redaction
│ │ ├── config.mjs config store + 3-tier API key resolution
│ │ ├── deepseek.mjs DeepSeek official API client (models / chat / balance / streaming)
│ │ ├── probes.mjs deterministic probe engine (the single source of truth)
│ │ ├── ai.mjs AI diagnosis layer (prompt + output validation)
│ │ ├── journal.mjs transaction journal + undo + rollback script generation
│ │ ├── repair.mjs repair action registry + transactional execution engine
│ │ └── index.mjs public entry point
│ ├── server.mjs local web console (HTTP + JSON API + SSE)
│ └── cli.mjs CLI entry point
├── ui/ console front-end (plain HTML/CSS/JS, no build step)
├── assets/
│ ├── icon.svg icon source (viewBox only — never add width/height)
│ └── dsh-fix.ico generated multi-size icon (6 frames, PNG-compressed)
├── tools/
│ ├── make-icon.mjs rebuild .ico from icon.svg
│ ├── check-ico.mjs independently validate the .ico container
│ ├── make-shortcut.ps1 create desktop / start-menu shortcuts
│ └── publish.ps1 maintainer preflight + publish (secret scan before any push)
├── test/ acceptance tests
└── config.json tool config (generated at runtime, holds your API key — never share)
Runtime data: journals/, reports/, backups/, quarantine/. All of it is git-ignored.
cd %USERPROFILE%\.dsh\dsh-fix
node src/cli.mjs status # show config and where the key comes from
node src/cli.mjs login --key sk-xxxxxxxx # verify and store your official API key
node src/cli.mjs models # fetch the official model list
node src/cli.mjs probe # probe only (no AI, costs nothing)
node src/cli.mjs diagnose # probe + AI diagnosis
node src/cli.mjs ui # open the local consoleOn Windows you can also just double-click dsh-fix.cmd.
pwsh -File tools\make-shortcut.ps1 # desktop (default name)
pwsh -File tools\make-shortcut.ps1 -StartMenu # also add to the Start menu
pwsh -File tools\make-shortcut.ps1 -Force # overwrite an existing one
pwsh -File tools\make-shortcut.ps1 -Name 'dsh-fix'The script reads the shortcut back and verifies the target and icon. Launching it twice is safe: if a console of this tool already owns the port, it just opens the browser instead of starting a second instance.
To change the icon, edit assets/icon.svg (and remember: do not add width/height to the root
element), then:
node tools/make-icon.mjs # rebuild the .ico
node tools/check-ico.mjs # validate the containermake-icon.mjs renders one 512px master image and downsamples it, then verifies the opaque-pixel
coverage of every frame (expected 70–95%) and fails loudly otherwise. Reason: headless Chrome on
Windows renders broken fragments for some window sizes — measured coverage drops to 45% / 14% / 0%
at 256 / 192 / 128 — so without that check you silently ship a corrupt icon.
Resolved in this order, first hit wins (the console always shows which one is in use):
- environment variable
DEEPSEEK_API_KEY api.apiKeyin~/.dsh/dsh-fix/config.jsonrefs.DEEPSEEK_API_KEYin~/.dsh/.credentials.yaml
So an already-configured machine works out of the box. login --mirror also writes the key back to
.credentials.yaml.
Keys are never echoed in clear text: the API only ever returns a mask (like sk-abc…wxyz), and
logs, evidence and AI output all pass through redaction.
The model list comes from the official GET /models. If that call fails, the tool falls back to a
built-in list and labels it source: fallback. /user/balance feeds the balance display so you are
never surprised about spend.
All read-only. Nothing is modified, no AI is called.
| Group | What is checked |
|---|---|
| runtime | Node version satisfies ^22.19.0 || >=24, dsh launcher, pnpm, git |
| home | DSH home exists / is writable, profiles/, settings.yaml, .credentials.yaml |
| profile | package.json parses, dsh.profile.bundles, YAML syntax of cordis.yml / cordis.patch.yml, pnpm-lock.yaml, MCP entries missing config |
| deps | missing dependencies, full node_modules integrity scan (placeholder packages / dangling symlinks), link: / file: / git &path: deps, plugin-declared dsh engine compatibility |
| config | settings.yaml parses, credential reference count, DeepSeek key visibility |
| service | port listening, HTTP health (401 counts as healthy — the service is up) |
| logs | error-signature lines in the last 7 days (module not found, syntax errors, port in use…) |
| disk | free space |
| recovery | available .bak-* backups, journal directory |
Health score: 100 - failures×12 - warnings×3, floored at 0.
The same check distinguishes fatal from merely untidy. For the node_modules scan:
- placeholder package (directory present,
package.jsonabsent) →fail: it breaks the plugin tree, the web app will not boot. - dangling symlink (left over from a directory that no longer exists) →
warn: if nothing requires it, it is just residue.
Without that split the tool would recommend reinstalling an entire dependency tree over a harmless leftover.
The AI may only pick actionIds from this list. It cannot supply a command to run.
Anything it proposes that is not registered is demoted to a "manual step" with a warning and is
never executed.
| actionId | Risk | Effect |
|---|---|---|
home.create-dirs |
low | create missing standard directories |
profile.create-cordis-patch |
low | create a missing cordis.patch.yml (contents []) |
logs.archive-old |
low | archive logs older than 30 days (journaled per file, reversible per file) |
backup.create |
low | full ~/.dsh backup (node_modules excluded) |
deps.prune-dangling-links |
low | remove dangling symlinks (rollback recreates them) |
profile.restore-yaml-backup |
medium | restore broken YAML from the newest .bak-* |
profile.restore-package-json |
medium | restore package.json from the newest .bak-* |
config.restore-settings-backup |
medium | restore settings.yaml from the newest .bak-* |
deps.install |
medium | run pnpm install |
deps.repair-broken |
medium | quarantine placeholder packages, then reinstall |
service.restart |
high | generate a restart script (generated only by default, not run) |
custom.script-N |
custom | run your own script registered under repair.customScripts |
Risk gating is enforced in code: medium needs allowMediumRisk, high needs allowHighRisk, both
off by default. A gated action is shown explicitly as "needs authorization" in the preview rather
than being silently skipped.
- Original files are copied to
backups/<journal-id>/and journaled before anything is changed. - Every step is appended to
journals/<journal-id>/journal.json, so even a crashed repair is reversible afterwards. - On completion a one-click rollback script is written to
~/.dsh/scripts/rollback-<task>-<timestamp>.ps1(UTF-8 BOM + CRLF so Chinese renders correctly under Windows PowerShell 5.1), and it is validated with PowerShell's own parser before being handed over — a one-click restore that does not run is worse than none. - The console's history tab can replay the rollback per journal, or regenerate the script.
Steps that cannot be undone automatically (like pnpm install) are listed explicitly as
"needs manual handling" in both the journal and the script.
status config, key source, recent repair history
models fetch the official model list + balance
login --key sk-… [--mirror]
set --model/--base-url/--profile/--port/--allow-medium/--allow-high
probe [--verbose] [--json] [--out FILE]
diagnose [--question "…"] [--model ID] [--json]
actions list actions (risk, applicability, authorization)
repair --actions a,b [--yes] dry-run by default; --yes actually applies
repair --ai [--yes] apply the AI's plan directly
journals / undo <id> / rollback-script <id>
ui [--port 3099] [--no-open]
selftest core self-check
node src/cli.mjs ui → http://127.0.0.1:3099/
Loopback only — this process can modify your dsh installation and should never be exposed. Four tabs:
- Probe & diagnose — one-click probe (free), streaming AI diagnosis, root causes and plan, "carry into repair page" to pre-select actions.
- Repair — action list with risk badges, authorization hints and preview commands. Preview is a dry-run; applying asks for confirmation.
- History & rollback — repair history, step detail, rollback-script generation, one-click undo.
- Connection & settings — verify/store the key, model and target profile, current state.
One command runs all three suites (recommended; leaves no trace in the real data directory):
node test/run-all.mjs # --keep preserves the temp state dir for debuggingIt spins up an isolated console of its own: DSH_HOME stays real (the probes must inspect the real machine) while DSH_FIX_HOME points at a temp directory (journals, reports, backups and quarantine all land there). The server is stopped and cleaned up afterwards.
Or run them individually (start a console yourself first):
node test/verify-core.mjs # probe / gating / dry-run / transactions / rollback / AI validation
node src/cli.mjs ui --port 3099
node test/verify-ui.mjs # HTTP API end-to-end (path traversal, unknown actions, risk gating)
node test/verify-ui-browser.mjs # real browser via CDP, incl. rendering and runtime-error checksDSH_FIX_HOME), pass the same DSH_FIX_HOME to
verify-ui.mjs: it creates a journal of its own to exercise "generate rollback script / undo", and a
mismatched state directory makes it fail with "journal not found". run-all.mjs already handles this.
verify-core.mjs builds a deliberately broken profile under testbed/ (bad YAML, missing deps, a
placeholder package, a dangling symlink, a broken link: dependency) and asserts that the probes
catch every one, that dry-run changes nothing, that a real repair is reversible, and that the
generated .ps1 parses under PowerShell and actually restores the state.
150 assertions across the three suites; currently all green.
- The AI cannot execute commands. It can read the probe report, emit a structured diagnosis, and
pick
actionIds from the registry. Nothing else. - Dry-run by default.
repairwithout--yes, or "Preview" in the console, touches nothing. - High-risk actions do not run by default.
service.restartonly generates a script; running it would kill the session you are currently in. - Keys are never echoed and never logged; evidence and AI text are redacted.
- Loopback only (127.0.0.1).
deps.install/deps.repair-brokenrewritenode_modules. pnpm's hard-linking will overwrite locally patched files, so re-run your own patch pipeline afterwards. The action description, the AI prompt and thecostNoteall say so.
- Probes judge structural integrity, not behavioural correctness. A package whose files are all present but whose contents are wrong will not be caught; that needs log signatures plus AI reasoning.
- YAML parsing reuses a
js-yamlalready present in the profile; if none is found it degrades to a built-in structural check (tab indentation, quote balance, bracket balance) and states which parser was used in the evidence. - Repair actions cover the environment/dependency layer. Bugs inside a plugin's own logic are out of scope for automatic repair.
- The desktop-shortcut and icon tooling is Windows-specific; the core CLI and probes are portable.
MIT