# dsh-session-cleaner-cli 🐳🧹 [δΈ­ζ–‡](README.md) [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![node](https://img.shields.io/badge/node-%3E%3D18-green)](package.json) [![test](https://github.com/ChenChen913/dsh-session-cleaner-cli/actions/workflows/test.yml/badge.svg)](https://github.com/ChenChen913/dsh-session-cleaner-cli/actions/workflows/test.yml) [![topic](https://img.shields.io/badge/topic-dsh--plugin-0969da?logo=github)](https://github.com/topics/dsh-plugin) **An offline CLI that deep-cleans DeepSeek Harness (DSH) workspace sessions**: list by workspace, delete with a trash bin, restore, prune ghost entries β€” keeping the workspace registry and projection cache in sync. Cross-platform (Windows / macOS / Linux), zero dependencies. --- ## Why DSH's session persistence is **append-only by design** β€” the GUI only offers *archive* (hide; data stays on disk) and exposes no delete at all (the `SessionPersistence` service has create/append/load/inspect/list, but no delete). Test conversations and abandoned chats pile up forever. This tool deletes sessions at both layers β€” the workspace registry (`storages/workspace.json`) and the log directories (`sessions///`) β€” so you can freely delete any conversation in any workspace. ## Quick start ```powershell # 1. Stop the GUI first (the tool refuses to delete while the server is running) # Windows: run stop-dsh.cmd in the deepseek-harness repo root # macOS / Linux: press Ctrl+C in the terminal running `dsh web`, or kill the process # 2. Interactive: pick a workspace β†’ select rows (1,3 / 2-5 / all) β†’ type DELETE to confirm node dsh-session-cleaner.mjs # 3. Restart the GUI (the sidebar reflects the new state after restart) ``` CLI mode: ```powershell node dsh-session-cleaner.mjs list # read-only listing of workspaces and sessions node dsh-session-cleaner.mjs delete [id...] # delete specific sessions (--yes skips the prompt) node dsh-session-cleaner.mjs restore # restore a previous deletion node dsh-session-cleaner.mjs trash list # inspect the trash bin node dsh-session-cleaner.mjs prune-ghosts # strip registry ids whose data dirs are gone ``` ## Install / Uninstall Zero dependencies; Node.js β‰₯ 18: ```powershell # Option 1: run directly from GitHub via npx (no clone needed) npx github:ChenChen913/dsh-session-cleaner-cli list # Option 2: clone git clone https://github.com/ChenChen913/dsh-session-cleaner-cli.git cd dsh-session-cleaner-cli node dsh-session-cleaner.mjs ``` Uninstall = delete the repository/script; if you no longer need recoverable data, also clear `~/.dsh/trash` and `~/.dsh/storages/backups`. ## Commands | Command | What it does | | --- | --- | | (no args) | Interactive: pick a workspace β†’ select sessions to delete | | `list` | List all workspaces and sessions (read-only; safe while the server runs) | | `list -w ` | Show one workspace only | | `delete [id...]` | Delete sessions (moves to trash by default; still requires typing `DELETE`) | | `restore ` | Restore a deletion batch (see `trash list`) | | `trash list` / `trash empty` | Inspect / empty the trash bin | | `prune-ghosts` | Strip registry ids whose session dirs are gone | Global flags: `--home ` (defaults to `$DSH_HOME` or `~/.dsh`), `--pid-file ` (extra dsh.pid location for the liveness check), `--dry-run`, `--purge` (wipe immediately, no trash), `--yes`, `--force` (skip the running-server guard). ## How it works DSH's default JSONL backend on disk: ``` ~/.dsh/ sessions///session.jsonl.zstd ← session log storages/workspace.json ← workspace registry + archive set storages/session_projcache.json ← title/stats projection cache ``` Deleting a session is a four-step transaction: 1. **Backup**: both registry files are copied to `storages/backups//` 2. **Move to trash**: log dir β†’ `.dsh/trash///` (`--purge` deletes directly) 3. **Sync registry**: remove the id from `sessionIds` and `archivedSessionIds` in `workspace.json`, stamp `updatedAt` 4. **Clean cache**: remove the `session_projcache.json` entry (rebuilt by the harness when the session is next opened) `restore` is the exact inverse: move the dir back, re-attach the id to its workspace, and restore the archived flag. One key safety fact: the DSH workspace entity's `sessionIds` getter filters by the live header index (`packages/workspace/workspace/src/entity.ts`), so even if a deleted id lingers in the registry, it is invisible after restart and pruned on the next registry write β€” the tool and the harness converge by design. ## Safety design - **Running-server guard**: delete/restore/prune refuse to run while `127.0.0.1:3080` is listening or a `dsh.pid` process is alive. The process check uses a cross-platform signal-0 probe β€” no platform commands - **Home validation**: mutating commands verify that `--home` actually looks like a DSH_HOME (`sessions/` or `storages/` present), so a mistyped path errors out instead of creating junk directories - **Mutex lock**: mutating commands hold `.dsh-session-cleaner.lock` (records the pid) so two instances can never overwrite each other's registry writes; stale locks from dead holders are taken over automatically - **Trash by default**: delete = move, not wipe; `restore` brings data back, and `trash empty` clears it for good - **Automatic backups**: both registry JSONs are backed up before every write - **Confirmation prompt**: interactive and CLI flows both require typing `DELETE` (opt out with `--yes`) - **Ghost self-healing**: registry ids pointing at missing data dirs are shown as "ghosts"; `prune-ghosts` strips them in one pass ## Cross-platform support | Platform | Default DSH_HOME | How to stop the GUI | Status | | --- | --- | --- | --- | | Windows | `C:\Users\\.dsh` | `stop-dsh.cmd` (or end the process in Task Manager) | Development environment; CI-covered | | macOS | `~/.dsh` | Ctrl+C in the terminal running `dsh web` | CI-covered | | Linux | `~/.dsh` | Ctrl+C in the terminal running `dsh web` | CI-covered | - The tool has zero platform dependencies: pure Node.js standard library, no native modules, no external commands - Liveness detection = port probe + signal-0 process probe (replaced the earlier Windows-only `tasklist` approach) - Session-dir scanning does not depend on how workspace paths are encoded (works with both the Windows `--C-Users-...--` encoding and Unix encodings) - CI (GitHub Actions) matrix: ubuntu / macos / windows Γ— Node 18 / 20 / 24 ## Configuration | Item | Description | | --- | --- | | `DSH_HOME` env var / `--home` | harness data directory; defaults to `~/.dsh` | | `--pid-file ` | extra dsh.pid location for the liveness check (non-standard installs) | | `--purge` | delete without trash | | `--dry-run` | preview only | | `--force` | skip the running-server guard (only when the GUI is confirmed stopped) | ## Permissions & data - **Reads/writes DSH_HOME only** (`~/.dsh`): never touches workspace project files or harness code - **No network requests**: the only network operation is a localhost port-3080 liveness probe - **No credentials**: `settings.yaml`, `.anonymous-user-id`, etc. are never read or written - **Attachments untouched**: images live in content-addressed shared storage (`attachments/v1`) and do not belong to a single session ## Compatibility - Verified against deepseek-harness mainline `47f943859bef60e4160492346772ded9b24f765a` (2026-08-13); storage formats `workspace.json` unit v2 and `session_projcache.json` unit v3 (the tool keeps unit blocks untouched and only edits fields) - Developed and tested on Windows 11 + Node 24; CI covers ubuntu / macos / windows Γ— Node 18 / 20 / 24 - Handles BOM-prefixed registry JSON; needs no compression tools (titles come from the projection cache, not from parsing zstd logs) ## Troubleshooting - **Garbled Chinese output**: run in Windows Terminal, or run `chcp 65001` first in legacy cmd - **"server is running" refusal**: stop the GUI first (Windows: stop-dsh.cmd; macOS/Linux: stop `dsh web`); use `--force` only when no server is up - **"does not look like a DSH_HOME"**: `--home` points at the wrong directory β€” check the path or the `DSH_HOME` env var - **"another dsh-session-cleaner process"**: a second cleaner instance is running; after confirming none is, remove `~/.dsh/.dsh-session-cleaner.lock` - **Titles missing after restore**: expected β€” the projection-cache entry is removed at delete time and rebuilt by the harness when the session is next opened - **Deleted a parent session**: subagent sessions are not in workspace accounting; they appear under "Ungrouped" β€” clean those up too ## Development ```powershell npm test # 13 end-to-end tests: list/delete/restore/purge/prune/interactive flow/running-server refusal/unknown id/lock/pid-file/home validation/incomplete batches ``` Tests drive the real CLI as a subprocess against scratch DSH_HOME fixtures and assert both filesystem and registry outcomes; CI lives in `.github/workflows/test.yml`. ## Ecosystem - Listed on the GitHub [`dsh-plugin` topic](https://github.com/topics/dsh-plugin) - Tracked by the [awesome-dsh-plugins radar](https://github.com/AdamPlatin123/awesome-dsh-plugins) (topic auto-discovery, 8h scans) - Curated list: [awesome-deepseek-harness](https://github.com/0xsline/awesome-deepseek-harness) - Related project: [fountunt/dsh-session-cleaner](https://github.com/fountunt/dsh-session-cleaner) β€” a plugin that deletes sessions inside the **running** web runtime (GUI delete button + `/api-ext/session.delete`). Complementary to this tool (offline CLI + trash/restore/ghost pruning): use the former for one-click in-GUI deletion, this one for bulk cleanup with recovery and ghost-accounting repair ## License & security [MIT](LICENSE) Β© 2026 ChenChen913. This tool handles no credentials; report security concerns via [GitHub Issues](https://github.com/ChenChen913/dsh-session-cleaner-cli/issues).