dsh-ssh-manage 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,234 @@
1
+ <div align="center">
2
+
3
+ <img src="icon.svg" width="96" height="96" alt="SSH Manager icon" />
4
+
5
+ # dsh-ssh-manage
6
+
7
+ **Manage SSH hosts from the DSH sidebar and open a real interactive terminal.**
8
+
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](package.json)
11
+ [![npm](https://img.shields.io/npm/v/dsh-ssh-manage.svg)](https://www.npmjs.com/package/dsh-ssh-manage)
12
+ [![DSH](https://img.shields.io/badge/DeepSeek%20Harness-plugin-4E7CF6)](https://github.com/deepseek-ai/deepseek-harness)
13
+ [![GitHub](https://img.shields.io/badge/github-sunChargedd%2Fdsh--plugins-181717?logo=github)](https://github.com/sunChargedd/dsh-plugins)
14
+
15
+ [中文](README.md) | **English**
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## What it is
22
+
23
+ `dsh-ssh-manage` is a Web plugin for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness). It turns the hosts in `~/.ssh/config` into manageable objects in the DSH sidebar:
24
+
25
+ - **Host management** — scan the config, groups, tags, favorites, notes, latency and host inspection;
26
+ - **Interactive terminal** — ssh2 allocates a PTY, xterm.js renders it, with colors, arrow keys, full-screen programs such as `vim`/`top`, and window resizing;
27
+ - **Day-to-day operations** — batch commands, SFTP browse/upload/download, connection tests;
28
+ - **Credentials** — one AES-256-GCM encrypted password per host, never written to `~/.ssh/config` and never returned to the browser;
29
+ - **Agent tool** — `ssh_manager` exposes the same capabilities to the model.
30
+
31
+ No more jumping between a terminal and DSH, and no need to remember where each host lives in the config file.
32
+
33
+ ## Features
34
+
35
+ ### Host management
36
+
37
+ - Group tree on the left, host table on the right: search, filter by group, favorite, tags, notes, connection state and latency.
38
+ - Create, edit, rename and delete hosts; renaming migrates group, tags, favorites, notes, inspection data and the saved password.
39
+ - Configurable table columns (info / notes / compact rows), responsive on narrow screens.
40
+ - Host details drawer: address, user, port, group, tags, notes, distro, CPU, memory, disk, latency, plus "Test connection" and "Inspect".
41
+ - Every write is backed up first, then written via a temp file and atomic rename; parse failures refuse the write instead of overwriting the config.
42
+ - Only managed fields are updated (`Host`, `HostName`, `User`, `Port`); `IdentityFile`, `ProxyJump`, `ForwardAgent`, `Include`, `LocalForward`, comments, blank lines and extra aliases are preserved.
43
+
44
+ ### Interactive terminal
45
+
46
+ - Click "Connect" to open a real PTY shell, not a one-command-at-a-time console.
47
+ - Multiple hosts stay as tabs inside the plugin page; switching tabs keeps output, closing a tab ends that session.
48
+ - xterm.js 6.0.0 rendering with 256 colors, cursor, scrollback, selection copy, paste and resize.
49
+ - Terminal theme follows the DSH light/dark theme; the session bar has quick actions for address, password status, test, files, details, clear and close.
50
+ - Output is streamed over a same-origin HTTP/SSE connection; input, resize and close use same-origin HTTP POST. A tested WebSocket channel is kept as a fallback.
51
+ - Clear status and a "Reconnect" button when the backend is unavailable — no silent hang.
52
+
53
+ ### Batch commands and files
54
+
55
+ - **Batch commands** — select hosts and run one command across them, with per-host stdout/stderr/exit code.
56
+ - **SFTP** — browse remote directories, upload and download from the host drawer, starting at the current user's home directory.
57
+ - **Connection test** — measures latency and writes it back to host metadata.
58
+ - **Host inspection** — distro, CPU cores, memory and disk capacity.
59
+
60
+ ### Credentials and data
61
+
62
+ - Passwords are AES-256-GCM encrypted in the plugin state directory and unlocked automatically at startup; no master password, no system Keychain.
63
+ - Passwords are only used for SSH password authentication (also compatible with hosts that only offer keyboard-interactive). They are never written to `~/.ssh/config`, command arguments, logs or responses.
64
+ - Ciphertext and key live in the same user directory, so this mainly protects against accidental file reads, not against malicious processes running as the same user.
65
+ - Groups, tags, favorites, notes and inspection data live in `hosts.json`; passwords live in `vault.json`; configuration changes are snapshotted to the backup directory.
66
+
67
+ > **Host key verification:** the transport does not verify `known_hosts` host keys (ssh2 accepts the server-provided key, same as the previous one-shot command path). **Use the interactive terminal only on trusted networks** and do not type sudo passwords on untrusted networks.
68
+
69
+ ### Agent tool
70
+
71
+ `ssh_manager` exposes: `overview`, `list_hosts`, `save_host`, `delete_host`, `set_metadata`, `save_password`, `delete_password`, `test_connection`, `inspect_host`, `exec`, `batch_exec`, `sftp_list`, `sftp_upload`, `sftp_download`.
72
+
73
+ The password parameter is write-only; no response contains a plaintext password.
74
+
75
+ ### Misc
76
+
77
+ - Light/dark themes, narrow layouts, all using DSH theme tokens.
78
+ - Reads non-wildcard `Host` entries from `~/.ssh/config`; `Include` and other directives are preserved verbatim (hosts defined inside `Include` are not expanded).
79
+ - Every Host, PTY and static-asset route is gated by DSH `connection.requestRejection`; unauthenticated requests get 401.
80
+ - No host or terminal data is sent to third-party services.
81
+
82
+ ## Usage
83
+
84
+ Sidebar → "SSH Hosts":
85
+
86
+ | Tab | Purpose |
87
+ | --- | --- |
88
+ | **Hosts** | Group tree and host table; "Connect" opens a terminal, clicking a row opens the details/files drawer. |
89
+ | **Sessions** | Open terminal tabs; switch, clear, reconnect, close. |
90
+ | **Batch** | Select hosts, run one command and review aggregated output. |
91
+ | **Settings** | Config path, backup directory, state directory, vault count, column preferences, compact rows. |
92
+
93
+ Creating a host requires alias, hostname and password. The password is not written to `~/.ssh/config`.
94
+
95
+ ## Installation
96
+
97
+ ### From npm (recommended)
98
+
99
+ npm package: [`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage) (current version `0.1.1`).
100
+
101
+ ```text
102
+ plugin_manager action=install_bundle target=dsh-ssh-manage@0.1.1
103
+ ```
104
+
105
+ Or with the DSH CLI:
106
+
107
+ ```bash
108
+ dsh plugin add dsh-ssh-manage@0.1.1
109
+ ```
110
+
111
+ After installation, refresh the Web page; the sidebar shows an "SSH Hosts" entry with id `dsh-ssh-manage`.
112
+
113
+ > Migrating from the old name `dsh-ssh-manager`: package name, plugin id and route prefix are now `dsh-ssh-manage`, but the state directory remains `~/.dsh/.ssh-manager/`, so saved hosts, passwords and metadata are preserved.
114
+
115
+ ### Local development
116
+
117
+ Link the repository directory into the DSH profile; code changes apply immediately (Host-side changes still need a DSH restart):
118
+
119
+ ```text
120
+ plugin_manager action=install_bundle target=<repository>/dsh-ssh-manage
121
+ ```
122
+
123
+ ### Manual
124
+
125
+ Add to the profile's `cordis.patch.yml`:
126
+
127
+ ```yaml
128
+ - insert:
129
+ - id: dsh-ssh-manage
130
+ name: 'dsh-ssh-manage'
131
+ config: {}
132
+ ```
133
+
134
+ and make sure the package is resolvable by the profile (for example `link:` to the local directory).
135
+
136
+ ## Compatibility
137
+
138
+ | | Requirement |
139
+ | --- | --- |
140
+ | **DSH** | **`>=0.2.1-rc.3 <0.3.0`** (validated on DSH Desktop `0.2.1-rc.3`) |
141
+ | **Node.js** | `>=20` (follows the DSH runtime) |
142
+ | **Platform** | Validated on macOS; the test suite only uses Node built-ins and runs cross-platform |
143
+ | **Network** | Reachable SSH port (22 by default, or the configured port) |
144
+
145
+ - `package.json` declares `engines.dsh`; DSH checks compatibility before loading and refuses an incompatible version.
146
+ - DSH extension points used: `sidebar.panellist` / `main` slots, `ctx.locale`, `ctx.webServer.register`, `ctx.webServer.registerUpgrade`, `ctx.webServer.tapIndex`, `ctx.tools.register`, `connection.requestRejection`.
147
+ - The Host half checks that `webServer.register` exists and throws a clear error otherwise.
148
+ - After installing or updating the plugin, the Host-side terminal routes (HTTP/SSE and the WebSocket fallback) and static-asset routes need a DSH restart; refreshing the page only updates browser code.
149
+
150
+ ## Architecture
151
+
152
+ ```
153
+ ┌───────────────────────── Browser ──────────────────────────┐
154
+ │ client.js │
155
+ │ sidebar.panellist entry + main panel (hosts/sessions/ │
156
+ │ batch/settings), xterm.js terminal, React, ctx.slots │
157
+ └─────────▲──────────────────────────────┬───────────────────┘
158
+ │ GET /dsh-ssh-manage/state │ POST /dsh-ssh-manage/action
159
+ │ GET /dsh-ssh-manage/pty/events │ POST /pty/open|input|resize|close
160
+ │ (SSE output) │ (input / resize / close)
161
+ ┌─────────┴──────────────────────────────▼───────────────────┐
162
+ │ index.js (Host plugin) │
163
+ │ routes · action dispatch · PTY sessions · assets · tool │
164
+ └───────┬──────────────────────┬──────────────────────┬──────┘
165
+ │ │ │
166
+ ┌───────▼────────┐ ┌─────────▼────────┐ ┌─────────▼─────────┐
167
+ │ lib/ssh-client │ │ lib/ssh-config │ │ lib/storage │
168
+ │ ssh2: PTY, │ │ parse / patch / │ │ state / metadata /│
169
+ │ exec, SFTP │ │ backup / atomic │ │ vault / backups │
170
+ └───────┬────────┘ └─────────┬────────┘ └─────────┬─────────┘
171
+ │ │ │
172
+ ~/.ssh/config ~/.ssh/config ~/.dsh/.ssh-manager/
173
+ ```
174
+
175
+ | File | Responsibility |
176
+ | --- | --- |
177
+ | `index.js` | Host plugin: HTTP/SSE/WS routes, action dispatch, PTY sessions, `ssh_manager` tool. |
178
+ | `client.js` | Browser plugin: sidebar entry, management page, xterm terminal; only `require('react')`. |
179
+ | `lib/ssh-client.js` | ssh2 password auth, PTY, exec, SFTP, inspection. |
180
+ | `lib/ssh-config.js` | `~/.ssh/config` parsing, in-place editing, backups, atomic saves. |
181
+ | `lib/storage.js` / `lib/vault.js` / `lib/metadata.js` | State directory, AES-256-GCM vault, host metadata. |
182
+ | `lib/ws.js` | Dependency-free RFC6455 WebSocket server (fallback PTY channel). |
183
+ | `assets/` | Vendored xterm.js 6.0.0, @xterm/addon-fit 0.11.0 and MIT licenses. |
184
+ | `scripts/test*.mjs` | Config/state/vault, WebSocket, PTY and SSE tests. |
185
+
186
+ ## Data and security
187
+
188
+ - Reads and writes only `~/.ssh/config`, the plugin state directory (default `~/.dsh/.ssh-manager/`, override with `DSH_SSH_MANAGER_HOME`) and the backup directory.
189
+ - Configuration writes are backed up first and use a temp file + atomic rename; only managed fields change, other content is preserved.
190
+ - `vault.json` uses AES-256-GCM with a key in the current user directory; `hosts.json` holds groups/tags/notes and other non-secret metadata.
191
+ - All Host / PTY / static-asset routes require an authenticated DSH browser session; unauthenticated requests return 401.
192
+ - Terminal traffic only flows between your browser and the target SSH host; no third-party service is involved.
193
+ - `known_hosts` host-key verification is not implemented; only use it on trusted networks for sensitive input.
194
+
195
+ ## Development
196
+
197
+ Node.js >= 20. The only runtime dependency is `ssh2`; there is no build step.
198
+
199
+ ```bash
200
+ cd dsh-ssh-manage
201
+ npm install
202
+ npm test
203
+ ```
204
+
205
+ | Command | Description |
206
+ | --- | --- |
207
+ | `npm run check` | Syntax checks + client model/i18n static guard |
208
+ | `node scripts/test.mjs` | SSH config parse/write, backups, vault, metadata, tool arguments |
209
+ | `node scripts/test-ws.mjs` | WebSocket handshake/framing/limits/strictness, PTY and asset routes |
210
+ | `node scripts/test-pty.mjs` | WebSocket PTY end-to-end against a real ssh2 mock server |
211
+ | `node scripts/test-sse.mjs` | HTTP/SSE PTY end-to-end against a real ssh2 mock server + 401 auth |
212
+ | `npm test` | All of the above |
213
+
214
+ The terminal uses vendored xterm.js 6.0.0 (MIT, see `assets/LICENSE.xterm`) and @xterm/addon-fit 0.11.0 (MIT, see `assets/LICENSE.addon-fit`).
215
+
216
+ ## Known limitations
217
+
218
+ - **Host key verification** is not implemented; ssh2 accepts the server-provided key. Use trusted networks only.
219
+ - **Session lifetime**: terminal sessions live in plugin page memory and reset when leaving the SSH panel, refreshing the page or HMR; page close sends best-effort close notifications, and orphaned sessions are cleaned up by the idle reaper.
220
+ - **Password auth only**: IdentityFile / SSH agent / ProxyJump are not used for transport; those config fields are preserved but ignored. ProxyJump hosts fail with a clear error.
221
+ - **Config editing scope**: the editor only changes `Host`, `HostName`, `User`, `Port` and plugin metadata; maintain other SSH directives in `~/.ssh/config`.
222
+ - **Batch commands** run one command; they are not an interactive terminal. Per-host output is capped at 1 MB and marked `truncated` when exceeded.
223
+ - **File size**: SFTP files are capped at 10 MB.
224
+ - **xterm assets**: the first terminal open loads about 500 KB of xterm.js from the same origin (no CDN).
225
+
226
+ ## Contributing
227
+
228
+ Issues and PRs are welcome, especially compatibility feedback for different SSH distributions and auth methods. Run `npm test` before submitting.
229
+
230
+ If this plugin helps you, a ⭐ Star is the best support.
231
+
232
+ ## License
233
+
234
+ [MIT](LICENSE) © 2026 Sun
package/README.md CHANGED
@@ -1,3 +1,236 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <img src="icon.svg" width="96" height="96" alt="SSH Manager icon" />
4
+
5
+ # dsh-ssh-manage
6
+
7
+ **在 DSH 侧边栏管理 SSH 主机,并直接打开真正的交互式终端。**
8
+
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](package.json)
11
+ [![npm](https://img.shields.io/npm/v/dsh-ssh-manage.svg)](https://www.npmjs.com/package/dsh-ssh-manage)
12
+ [![DSH](https://img.shields.io/badge/DeepSeek%20Harness-plugin-4E7CF6)](https://github.com/deepseek-ai/deepseek-harness)
13
+ [![GitHub](https://img.shields.io/badge/github-sunChargedd%2Fdsh--plugins-181717?logo=github)](https://github.com/sunChargedd/dsh-plugins)
14
+
15
+ **中文** | [English](README.en.md)
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## 这是什么
22
+
23
+ `dsh-ssh-manage` 是 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 的 Web 插件。它把 `~/.ssh/config` 里的主机变成 DSH 侧边栏里可管理、可连接、可运维的对象:
24
+
25
+ - **主机管理**:扫描配置、分组、标签、收藏、备注、连接延迟、主机巡检;
26
+ - **交互式终端**:ssh2 分配 PTY,xterm.js 渲染,支持彩色输出、方向键、`vim`/`top` 等全屏程序和窗口自适应;
27
+ - **日常运维**:批量命令、SFTP 目录浏览 / 上传 / 下载、测试连接;
28
+ - **凭据管理**:每台主机保存一份 AES-256-GCM 加密密码,不写进 `~/.ssh/config`,也不返回给浏览器;
29
+ - **Agent 工具**:`ssh_manager` 把同一套能力开放给模型。
30
+
31
+ 不用再在终端和 DSH 之间来回切换,也不用记每个主机在配置文件里的位置。
32
+
33
+ ## 特性
34
+
35
+ ### 主机管理
36
+
37
+ - 左侧分组树 + 右侧主机表格:搜索、按分组筛选、收藏、标签、备注、连接状态与延迟一目了然。
38
+ - 新建、编辑、重命名、删除主机;重命名自动迁移分组、标签、收藏、备注、巡检信息和已保存密码。
39
+ - 表格列可配置(信息列 / 备注列 / 紧凑行高),窄屏自适应。
40
+ - 主机详情抽屉:地址、用户、端口、分组、标签、备注、发行版、CPU、内存、磁盘、延迟,以及「测试连接」「巡检」。
41
+ - 所有写操作先备份、再临时文件 + 原子重命名;解析失败时拒绝写入,不覆盖原配置。
42
+ - 只更新受管字段(`Host`、`HostName`、`User`、`Port`);`IdentityFile`、`ProxyJump`、`ForwardAgent`、`Include`、`LocalForward`、注释、空行和其它别名原样保留。
43
+
44
+ ### 交互式终端
45
+
46
+ - 点「连接」打开真正的 PTY shell,不是一条命令一条命令地执行。
47
+ - 多台主机以标签页常驻在当前插件页面,切换标签不丢输出;关闭标签才结束会话。
48
+ - xterm.js 6.0.0 渲染,支持 256 色、光标、滚动回看、选中复制、粘贴、窗口 resize。
49
+ - 终端主题跟随 DSH 明暗主题;会话顶部有地址、密码状态、测试连接、文件、详情、清屏、关闭等快捷操作。
50
+ - 输出走同源 HTTP/SSE 长连接,输入 / 尺寸 / 关闭走同源 HTTP POST;插件同时保留经过测试的 WebSocket 备用通道。
51
+ - 断线或后端未加载时显示明确的状态与「重新连接」,不会静默卡死。
52
+
53
+ ### 批量命令与文件
54
+
55
+ - **批量命令**:勾选多台主机,一次执行同一条命令,按主机汇总 stdout / stderr / 退出码。
56
+ - **SFTP**:在主机抽屉里浏览远程目录、上传、下载;默认从当前用户主目录开始。
57
+ - **测试连接**:记录连接延迟,结果写回主机信息。
58
+ - **主机巡检**:读取发行版、CPU 核心数、内存、磁盘容量。
59
+
60
+ ### 凭据与数据
61
+
62
+ - 密码使用 AES-256-GCM 加密保存在插件自己的状态目录,插件启动时自动解锁,不需要每次输入主密码,也不使用系统 Keychain。
63
+ - 密码只用于 SSH password authentication,同时兼容只提供 keyboard-interactive 的主机;不会写入 `~/.ssh/config`、命令参数、日志或返回给浏览器。
64
+ - 密码密文与本地密钥位于同一用户目录,主要防止文件被直接误读,不能抵御同一用户下的恶意进程。
65
+ - 主机分组、标签、收藏、备注、巡检结果保存在 `hosts.json`;密码保存在 `vault.json`;删除/修改配置前写入备份目录。
66
+
67
+ > **主机密钥校验**:当前传输未接入 `known_hosts` 主机密钥校验,ssh2 默认接受服务器提供的主机密钥(与之前的单条命令执行一致)。**请在可信网络中使用交互式终端**,不要在不可信网络中输入 sudo 密码等敏感信息。
68
+
69
+ ### Agent 工具
70
+
71
+ `ssh_manager` 工具开放以下动作,和页面走同一套 Host 逻辑:
72
+
73
+ `overview`、`list_hosts`、`save_host`、`delete_host`、`set_metadata`、`save_password`、`delete_password`、`test_connection`、`inspect_host`、`exec`、`batch_exec`、`sftp_list`、`sftp_upload`、`sftp_download`。
74
+
75
+ 密码字段是 write-only,任何返回值都不包含密码明文。
76
+
77
+ ### 其它
78
+
79
+ - 深色 / 浅色主题、窄屏布局,全部使用 DSH 主题 token。
80
+ - 读取 `~/.ssh/config` 中的非通配符 `Host` 条目;`Include` 及其它指令原样保留(不会展开 `Include` 内定义的主机)。
81
+ - 所有 Host 路由、PTY 路由、静态资源路由都经过 DSH 的 `connection.requestRejection` 鉴权,未认证请求返回 401。
82
+ - 不向第三方服务发送任何主机或终端数据。
83
+
84
+ ## 使用
85
+
86
+ 左侧栏 →「SSH 主机」:
87
+
88
+ | 标签页 | 用途 |
89
+ | --- | --- |
90
+ | **主机** | 分组树 + 主机表格;点「连接」进入终端,点行打开详情/文件抽屉。 |
91
+ | **会话** | 已打开的终端标签页;可切换、清屏、重连、关闭。 |
92
+ | **批量命令** | 勾选主机后执行同一条命令,查看汇总结果。 |
93
+ | **设置** | 配置文件、备份目录、状态目录、凭据数量、列显示、紧凑行高。 |
94
+
95
+ 新建主机必填:别名、服务器地址、密码。密码不会写入 `~/.ssh/config`。
96
+
97
+ ## 安装
98
+
99
+ ### 从 npm 安装(推荐)
100
+
101
+ npm 包:[`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage)(当前版本 `0.1.1`)。
102
+
103
+ ```text
104
+ plugin_manager action=install_bundle target=dsh-ssh-manage@0.1.1
105
+ ```
106
+
107
+ 或使用 DSH CLI:
108
+
109
+ ```bash
110
+ dsh plugin add dsh-ssh-manage@0.1.1
111
+ ```
112
+
113
+ 安装后刷新 Web 页面,左侧栏会出现「SSH 主机」入口,入口 id 为 `dsh-ssh-manage`。
114
+
115
+ > 从旧名 `dsh-ssh-manager` 迁移:包名、插件 id、路由前缀已改为 `dsh-ssh-manage`,但状态目录仍保留 `~/.dsh/.ssh-manager/`,已保存的主机、密码和元数据不会丢失。
116
+
117
+ ### 本地开发安装
118
+
119
+ 把仓库目录 link 进 DSH profile,改代码即时生效(Host 侧改动仍需重启 DSH):
120
+
121
+ ```text
122
+ plugin_manager action=install_bundle target=<仓库绝对路径>/dsh-ssh-manage
123
+ ```
124
+
125
+ ### 手动
126
+
127
+ 在 profile 的 `cordis.patch.yml` 中加入:
128
+
129
+ ```yaml
130
+ - insert:
131
+ - id: dsh-ssh-manage
132
+ name: 'dsh-ssh-manage'
133
+ config: {}
134
+ ```
135
+
136
+ 并确保包可被 profile 解析(例如 `link:` 到本地目录)。
137
+
138
+ ## 兼容性
139
+
140
+ | | 要求 |
141
+ | --- | --- |
142
+ | **DSH** | **`>=0.2.1-rc.3 <0.3.0`**(实机验证版本:DSH Desktop `0.2.1-rc.3`) |
143
+ | **Node.js** | `>=20`(跟随 DSH 内置运行时) |
144
+ | **平台** | 实机验证 macOS;测试套件只依赖 Node 内置模块,可跨平台运行 |
145
+ | **网络** | 需要能访问目标 SSH 主机的 22 端口(或配置的端口) |
146
+
147
+ - `package.json` 声明了 `engines.dsh`;DSH 在安装/启动前会做兼容性检查,不满足时阻止加载并提示。
148
+ - 用到的 DSH 扩展点:`sidebar.panellist` / `main` 槽位、`ctx.locale`、`ctx.webServer.register`、`ctx.webServer.registerUpgrade`、`ctx.webServer.tapIndex`、`ctx.tools.register`、`connection.requestRejection`。
149
+ - Host 侧在激活时会检查 `webServer.register` 是否存在,缺失时抛出明确错误。
150
+ - 安装或更新插件后,Host 侧的终端传输路由(HTTP/SSE 与备用 WebSocket)和静态资源路由需要重启 DSH 才会生效;仅刷新页面只会更新浏览器端代码。
151
+
152
+ ## 架构
153
+
154
+ ```
155
+ ┌───────────────────────────── 浏览器 ─────────────────────────────┐
156
+ │ client.js │
157
+ │ sidebar.panellist 入口 + main 面板(主机/会话/批量/设置) │
158
+ │ xterm.js 终端 · React(module table)· ctx.slots / ctx.locale │
159
+ └───────────▲───────────────────────────────┬──────────────────────┘
160
+ │ GET /dsh-ssh-manage/state │ POST /dsh-ssh-manage/action
161
+ │ GET /dsh-ssh-manage/pty/events │ POST /pty/open|input|resize|close
162
+ │ (SSE 输出) │ (输入 / 尺寸 / 关闭)
163
+ ┌───────────┴───────────────────────────────▼──────────────────────┐
164
+ │ index.js(Host 插件) │
165
+ │ 路由 · action 分发 · PTY 会话管理 · 静态资源 · ssh_manager 工具 │
166
+ └───────┬──────────────────────┬──────────────────────┬────────────┘
167
+ │ │ │
168
+ ┌───────▼────────┐ ┌─────────▼────────┐ ┌─────────▼─────────┐
169
+ │ lib/ssh-client │ │ lib/ssh-config │ │ lib/storage │
170
+ │ ssh2 连接/PTY/ │ │ 解析 / 局部写入 │ │ 状态 / 元数据 / │
171
+ │ exec / SFTP │ │ 备份 / 原子保存 │ │ vault / 备份 │
172
+ └───────┬────────┘ └─────────┬────────┘ └─────────┬─────────┘
173
+ │ │ │
174
+ ~/.ssh/config ~/.ssh/config ~/.dsh/.ssh-manager/
175
+ ```
176
+
177
+ | 文件 | 职责 |
178
+ | --- | --- |
179
+ | `index.js` | Host 插件:HTTP/SSE/WS 路由、action 分发、PTY 会话、`ssh_manager` 工具。 |
180
+ | `client.js` | 浏览器插件:侧边栏入口、管理页、xterm 终端;只 `require('react')`。 |
181
+ | `lib/ssh-client.js` | ssh2 密码认证、PTY、exec、SFTP、巡检。 |
182
+ | `lib/ssh-config.js` | `~/.ssh/config` 解析、局部写入、备份、原子保存。 |
183
+ | `lib/storage.js` / `lib/vault.js` / `lib/metadata.js` | 状态目录、AES-256-GCM 凭据仓库、主机元数据。 |
184
+ | `lib/ws.js` | 无依赖 RFC6455 WebSocket 服务端(备用 PTY 通道)。 |
185
+ | `assets/` | 随插件分发的 xterm.js 6.0.0、@xterm/addon-fit 0.11.0 与 MIT License。 |
186
+ | `scripts/test*.mjs` | 配置/状态/vault、WebSocket、PTY、SSE 的自动化测试。 |
187
+
188
+ ## 数据与安全
189
+
190
+ - 只读写 `~/.ssh/config`、插件状态目录(默认 `~/.dsh/.ssh-manager/`,可用 `DSH_SSH_MANAGER_HOME` 覆盖)和备份目录。
191
+ - 写配置前备份,写入采用临时文件 + 原子重命名;只修改受管字段,其余内容尽量保持原样。
192
+ - 状态目录中的 `vault.json` 使用 AES-256-GCM 加密,密钥在当前用户目录;`hosts.json` 保存分组/标签/备注等非敏感元数据。
193
+ - 所有 Host / PTY / 静态资源路由都要求 DSH 浏览器会话认证,未认证请求返回 401。
194
+ - 终端输出仅在你自己的浏览器与目标 SSH 主机之间传输,不经过第三方服务。
195
+ - 当前未实现 `known_hosts` 主机密钥校验;请只在可信网络输入敏感信息。
196
+
197
+ ## 开发
198
+
199
+ 要求 Node.js >= 20。运行时依赖只有 `ssh2`,无构建步骤。
200
+
201
+ ```bash
202
+ cd dsh-ssh-manage
203
+ npm install
204
+ npm test
205
+ ```
206
+
207
+ | 命令 | 说明 |
208
+ | --- | --- |
209
+ | `npm run check` | 所有源文件语法检查 + client model/i18n 静态守卫 |
210
+ | `node scripts/test.mjs` | SSH config 解析/写入、备份、vault、元数据、工具参数 |
211
+ | `node scripts/test-ws.mjs` | WebSocket 握手/分帧/分片上限/严格校验、PTY 与静态资源路由 |
212
+ | `node scripts/test-pty.mjs` | 真实 ssh2 mock 服务器上的 WebSocket PTY 端到端 |
213
+ | `node scripts/test-sse.mjs` | 真实 ssh2 mock 服务器上的 HTTP/SSE PTY 端到端 + 401 鉴权 |
214
+ | `npm test` | 以上全部 |
215
+
216
+ 终端组件使用随插件分发的 xterm.js 6.0.0(MIT,见 `assets/LICENSE.xterm`)与 @xterm/addon-fit 0.11.0(MIT,见 `assets/LICENSE.addon-fit`)。
217
+
218
+ ## 已知限制
219
+
220
+ - **主机密钥校验**:未接入 `known_hosts`,ssh2 默认接受服务器主机密钥;仅在可信网络使用。
221
+ - **会话生命周期**:终端会话保存在插件页面内存中,离开 SSH 插件面板、页面刷新或 HMR 会重置;页面关闭时会尽力发送关闭通知,异常崩溃留下的孤儿会话由空闲清理兜底。
222
+ - **仅密码认证**:不支持 IdentityFile / SSH agent / ProxyJump 传输;配置中的这些字段会原样保留,但不会用于连接。ProxyJump 主机会明确报错。
223
+ - **配置编辑范围**:编辑器只修改 `Host`、`HostName`、`User`、`Port` 与插件元数据;其它 SSH 指令请在 `~/.ssh/config` 中维护。
224
+ - **批量命令**:一次执行一条命令,不等同于交互式终端;单台输出上限 1 MB,超出会标记 `truncated`。
225
+ - **文件大小**:SFTP 单文件上限 10 MB。
226
+ - **xterm 资源**:首次打开会加载约 500 KB 的 xterm.js 资源(本地同源,不访问 CDN)。
227
+
228
+ ## 参与贡献
229
+
230
+ 欢迎 Issue、PR,以及不同 SSH 发行版 / 认证方式的兼容性反馈。提交前请先跑 `npm test`。
231
+
232
+ 如果这个插件帮到了你,点个 ⭐ Star 是最好的支持。
233
+
234
+ ## 许可证
235
+
236
+ [MIT](LICENSE) © 2026 Sun
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
2
+ Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
3
+ Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core._renderService.dimensions;if(0===e.css.cell.width||0===e.css.cell.height)return;const t=0===this._terminal.options.scrollback?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),i=parseInt(r.getPropertyValue("height")),o=Math.max(0,parseInt(r.getPropertyValue("width"))),s=window.getComputedStyle(this._terminal.element),n=i-(parseInt(s.getPropertyValue("padding-top"))+parseInt(s.getPropertyValue("padding-bottom"))),l=o-(parseInt(s.getPropertyValue("padding-right"))+parseInt(s.getPropertyValue("padding-left")))-t;return{cols:Math.max(2,Math.floor(l/e.css.cell.width)),rows:Math.max(1,Math.floor(n/e.css.cell.height))}}}})(),e})()));
2
+ //# sourceMappingURL=addon-fit.js.map