dsh-terminal
Workspace-aware web terminal plugin for the DeepSeek Harness (dsh). Runs a streaming PTY terminal at /terminal and embeds a split-pane terminal dock powered by xterm.js.
安装
dsh plugin --profile web add github:emircanerkul/dsh-terminal
需要可复现安装时,可在仓库后追加 #commit 固定提交。
Workspace-aware web terminal plugin for the DeepSeek Harness (dsh). Runs a streaming PTY terminal at /terminal and embeds a split-pane terminal dock powered by xterm.js.
该插件未提供要点说明,请参考仓库 README。
- 安装并启动 DeepSeek Harness:
npx @deepseek-ai/dsh web - 在终端执行上面的安装命令(CLI 会解析插件并核验来源)
- 用 dsh plugins list 确认已安装,必要时重启 Harness 生效
插件以当前 dsh 进程的权限运行,安装时可能执行代码。请先通读仓库源码与许可证,确认无破坏性命令与越权访问;本站只做索引,不对第三方插件安全性作担保。
| 代码仓库 | github.com/emircanerkul/dsh-terminal |
| 许可证 | MIT |
| 主要语言 | main |
| 下载量 | 2 |
| GitHub 星标 | 0 |
| 最近推送 | 2026-08-20 |
| 收录日期 | 2026-09-19 |
| 分类 | 工具与能力 |
事实信息来自公开插件目录快照(2026-10-01),介绍文案由本站再加工。
以下为插件仓库 README 全文(原始内容,由公开目录抓取整理)。
# dsh-terminal
Workspace-aware web terminal plugin for the [DeepSeek Harness](https://github.com/deepseek-ai) (dsh).
Loaded as a **host plugin** by the `web` profile: it serves a terminal page at
`/terminal` (streaming PTY output over SSE, keystrokes via POST) and embeds a
**split-pane terminal dock** into the chat column so you can run `nvim`/`lazygit`
on the workspace you're working in without leaving the page.
## Demo
A quick tour of the workspace-aware terminal dock.
[图片: dsh-terminal demo]
## What makes it "workspace-aware"
- **One PTY per workspace root**, kept alive across SSE disconnects — switching
conversations preserves each workspace's terminal and scrollback.
- Sessions are bounded by an LRU cap (default 8) **and** an idle reaper, so
never-again-visited terminals are eventually freed.
- The dock targets whatever chat is *active* by asking the server for its
authoritative workspace root (validated against the real workspace registry).
## Installation
Install directly from the GitHub repository:
```sh
dsh plugin --profile web add github:emircanerkul/dsh-terminal
dsh web
```
Or clone it locally and install from the local directory:
```sh
git clone https://github.com/emircanerkul/dsh-terminal.git
cd dsh-terminal
npm install
npm run check
dsh plugin --profile web add .
dsh web
```
## Loading
The web profile mounts this plugin through a patch layer and resolves it as a
**package by name** (so DSH's runtime client-plugin discovery can find its
`dsh.client` half — the Settings → Plugins card). In `cordis.patch.yml`:
```yaml
- insert:
- id: terminal
name: 'dsh-terminal'
```
with `dsh-terminal` linked into the profile's shared install so both the
loader and the client-modules scanner resolve it:
```sh
ln -s /absolute/path/to/dshterm ~/.dsh/profiles/node_modules/dsh-terminal
```
The plugin declares a client half in `package.json` (`dsh.client` +
`exports["./client"]`, implemented in `client.js`) that contributes a
Settings → Plugins card; the host serves the config through
`GET/POST /terminal/shortcuts` and keeps it in a small file store
(`~/.dsh-terminal/shortcuts.json`), so the plugin stays self-contained and
never waits on the shared settings service. See `src/index.js` for the
plugin row (`inject: ['webServer', 'sandboxPolicy']`).
## Layout
```
terminal.mjs entry point (re-exports src/index.js — the mounted path)
src/ host-side server code
index.js plugin assembly + teardown, idle-reaper timer
constants.js limits, MIME table, asset manifest, path roots, knobs
palette.js the single fixed terminal palette (no theme switcher)
pty.js single node-pty accessor
sessions.js PTY lifecycle manager (LRU + idle reaper)
http.js body reader / static server / JSON-text response helpers
workspace.js workspace-root resolution helpers
auth.js page-token mint + ?token= check helpers
page.js /terminal page HTML builder
routes/ all HTTP routes + the chat-column embed tap (split by area)
index.js installRoutes() wiring + shared route helpers
terminal.js /terminal, /stream, /input, /resize, /kill
api.js /bin, /workspace, /debug, /sessions
assets.js /terminal/assets/*
embed-tap.js chat-column embed injection
web/ browser-side assets served at /terminal/assets/*
embed.js the chat-column SPLIT-PANE dock (client LRU of iframes)
terminal/ terminal page bootstrap + vendored xterm
fonts/ Nerd Font used for nvim/lazygit PUA icons
test/ unit tests (TerminalSessions) + a module smoke harness
```
## Config / static-asset map
`src/constants.js` holds the knob values (`MAX_SESSIONS`, `MAX_CLIENTS_PER_SESSION`,
`IDLE_MS`, `REAPER_MS`, `MAX_INPUT_BYTES`, `MAX_RESIZE_BYTES`) and the `ASSETS`
manifest that maps public `/terminal/assets/` URLs to files under `web/`.
## HTTP surface (authed = `?token=` from the /terminal page)
| Method | Path | Purpose |
|--------|--------------------|-----------------------------------------------------|
| GET | `/terminal` | the terminal page (mints the auth token) |
| GET | `/terminal/stream` | SSE: PTY output for the workspace |
| POST | `/terminal/input` | keystrokes into the PTY |
| POST | `/terminal/resize` | resize the PTY |
| GET | `/terminal/bin` | is a command (lazygit/nvim) on PATH? |
| GET | `/terminal/proc` | the executable currently occupying this workspace's terminal (or null) |
| GET | `/terminal/debug` | last embed-reported detection diagnostic |
| GET | `/terminal/workspace`| authoritative active workspace root + list |
| GET | `/terminal/sessions`| live per-workspace PTY debug listing |
| GET | `/terminal/shortcuts`| effective dock hotkeys (Settings → Plugins → Terminal) |
| POST | `/terminal/kill` | kill one workspace's terminal |
| GET | `/terminal/assets/*`| static files (embed, bootstrap, xterm, fonts) |
## Keyboard shortcuts
The dock registers global shortcuts that fire whatever has focus — chat, sidebar,
or the terminal itself (the config is relayed into the terminal page, which
captures matching combos before xterm/lazygit see them and asks the dock to act):
| Action | Default | Notes |
|-------------------|----------------|----------------------------------------------|
| Toggle dock | `Ctrl+\`` | restores the last size/position (persisted) |
| Toggle popup mode | `Ctrl+Shift+\`` | open/close the full-screen floating modal |
| Open lazygit | `Ctrl+Shift+G` | → `lazygit`, only at an idle shell prompt |
| Open nvim | `Ctrl+Shift+E` | → `nvim .`, only at an idle shell prompt |
Toggling back open restores the previous size and split because those are
persisted per workspace. The launchers first ask `/terminal/proc`, which scans
*this workspace's* PTY process tree for **any** non-shell program (lazygit, nvim,
vim, htop, …). If one is running — whatever it is — the shortcut does NOT type the
new command; instead it shows a short toast telling you to close the running app
first (`Ctrl+C` / `:q`). So pressing `Ctrl+Shift+E` while lazygit is up won't type
`nvim .` over it — it toasts "running lazygit — close it first". A lazygit in
another workspace or a separate terminal never counts: each `/terminal/proc` call
walks only the active workspace's PTY.
Shortcuts are **layout-independent**: each matches `event.key` *or*
`event.code` (the physical key, and multiple codes are accepted). On US the key
left of `1` is `Backquote`; on a UK/ISO ("British PC") layout that key reports
`IntlBackslash` (yielding `key="0"` under Ctrl), so the toggle matches both, so `Ctrl+` `` ` ``
toggles the dock there too. `mod` is one of `ctrl` | `meta` | `alt` | `any`; note
`Cmd+` `` ` `` is the OS "cycle windows" shortcut on macOS, so a `meta` default
would never reach the page — `ctrl` is the safe cross-platform default. Each
binding is set by `key` (character), `code` (physical key; several codes may be
given), or both. Toggle/modal accept both the US `Backquote` and the UK/ISO
`IntlBackslash` physical codes.
**Configure bindings from Settings → Plugins → Terminal.** The Settings card
reads the current bindings from `GET /terminal/shortcuts` and saves them via
`POST /terminal/shortcuts`; the host persists them to
`~/.dsh-terminal/shortcuts.json`. The dock fetches the effective config from
`GET /terminal/shortcuts` on every page load and merges it over the defaults in
`web/embed.js` (no localStorage override path any more). `web/embed.js` is
loaded fresh per page, so after saving in the settings panel a plain browser
refresh picks the new bindings up.
## Development
```sh
npm run check # syntax-check every source + web asset
npm test # unit tests for PTY lifecycle (TerminalSessions)
node test/smoke.mjs # mount the plugin against a mock ctx (wiring smoke test)
```
**Reload behaviour (important):**
- `web/embed.js` is injected as a tiny loader that pulls `/terminal/assets/embed.js`
fresh from disk on every page load — **embed edits go live on a plain browser refresh**.
- `web/terminal/boot.js` is read **once at module load** and baked into the
`/terminal` page, so **boot.js edits require a web-profile restart**.
- Server-side edits under `src/**` also require a web-profile restart.
- The Settings → Plugins → Terminal card comes from the `dsh.client` half, so
it appears only after a web-profile restart realigns loader entry names and
the browser loads the new client bundle (refresh the page too). From then on,
saved bindings go live in the dock on a refresh.
## Sponsor
[图片: Erklab]
Sponsored by [erklab](https://erklab.com) — Architected with production-grade systems using AI-driven velocity and human-centered precision.
数据来源:公开的 DeepSeek Harness 插件目录与各插件 GitHub 仓库。本站为独立第三方目录,与 DeepSeek、幻方(High-Flyer)及插件作者均无隶属或背书关系。