@hyzyn/dsh-tty 0.1.1 → 0.2.0

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 dsh-plugin-kit contributors
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.md CHANGED
@@ -3,7 +3,10 @@
3
3
  DSH Web GUI 的**终端面板**插件:侧边栏「终端」入口打开一个大弹窗,内嵌
4
4
  xterm.js 全交互终端(node-pty 真实 PTY),支持**多标签页**,可运行任意
5
5
  命令与 TUI 程序(vim / htop / dev server 等)。浏览器半体打包了 xterm
6
- 内核,宿主半体经 WebSocket 与 PTY 会话双向透传。
6
+ 内核,宿主半体经 WebSocket 与 PTY 会话双向透传。0.2.0 起支持 **SSH 连接
7
+ (方案 C)**:`ssh2` 原生直连远程主机,像本地终端一样交互(见下文)。
8
+
9
+ ![终端面板:多标签页 xterm 弹窗,工具栏含搜索/清屏/复制/粘贴,标题栏含最小化「—」与关闭 ✕](../../docs/dsh-plugin-kit-tty.png)
7
10
 
8
11
  ## 安装
9
12
 
@@ -18,29 +21,70 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
18
21
  ## 使用
19
22
 
20
23
  - 打开面板自动创建第一个终端(默认 `$SHELL`,macOS 上通常是 zsh);
21
- - **多标签页**:标签栏「+」新建终端、标签 ✕ 关闭;每个标签独立 PTY 会话;
24
+ - **多标签页**:标签栏「+」新建终端(0.2.0 起「+」为菜单:本地终端 /
25
+ SSH 连接簿 / SSH 连接…,SSH 见下节)、标签 ✕ 关闭;每个标签独立会话
26
+ (本地 PTY 或 SSH channel);
22
27
  - **工作目录跟随当前 DSH 会话**:新标签默认在当前会话工作目录打开
23
28
  (宿主配置 `cwd` 作兜底);
24
29
  - 支持 vim / htop / less 等 TUI(TERM 已注入为 `xterm-256color`);
25
30
  - 面板大小变化自动 resize(xterm fit → PTY 原生 resize);
26
- - **Ctrl+F 终端内搜索**(Enter 下一个 / Shift+Enter 上一个 / Esc 关闭),
31
+ - **Ctrl+F 终端内搜索**(Enter 下一个 / Shift+Enter 上一个 / Esc 只关搜索框),
27
32
  输出中的链接可点击,工具栏提供 清屏 / 复制选中 / 粘贴;
28
- - 关闭面板或按 Esc 结束全部会话(PTY 树级清理);会话退出后点终端区域可重开;
33
+ - **最小化(状态并入侧边栏入口)**:点弹窗外空白处、按 Esc 或标题栏「—」
34
+ 把面板收起——PTY 会话与输出缓冲保持存活,侧边栏「终端」入口上显示
35
+ 「运行中/总数」徽标与状态点(有输出时脉冲提示),点击入口即可恢复;
36
+ 悬浮条 ✕ / 标题栏 ✕ 才真正关闭并结束全部会话;
37
+ - 标题栏 ✕ 关闭面板并结束全部会话(PTY 树级清理);会话退出后点终端区域可重开;
29
38
  - 并发上限默认 4(配置 `maxSessions`,1~16)。
30
39
 
40
+ ![终端面板设置卡片:shell / TERM / 并发上限等保存即热生效](../../docs/dsh-plugin-kit-tty-setting.png)
41
+
31
42
  ## agent 工具(P1)
32
43
 
33
44
  插件向 agent 注入三个工具(与 bash 工具同权,操作实时显示在用户终端里):
34
45
 
35
46
  | 工具 | 作用 |
36
47
  | --- | --- |
37
- | `tty_list` | 列出活跃终端会话(sid / pid / cwd / 活动时间) |
48
+ | `tty_list` | 列出活跃终端会话(sid / kind(local\|ssh)/ target / pid / cwd / 活动时间;SSH 会话无 pid,显示 target) |
38
49
  | `tty_capture` | 读取指定会话的近期输出(尾部 N 行,默认 60)——查看 dev server / build 日志 |
39
50
  | `tty_send` | 向指定会话发送按键/文本(如 dev server 的 q 键、菜单选择) |
40
51
 
41
52
  典型用法:用户在终端面板里跑了 `pnpm dev`,agent 用 `tty_list` 找到 sid →
42
53
  `tty_capture` 看日志 → `tty_send` 发 `q` 停止。
43
54
 
55
+ SSH 会话同表调度:`tty_list` 里 `kind: 'ssh'` 的条目按 `target`
56
+ (user@host[:port])识别,`tty_capture` / `tty_send` 用法与本地会话完全
57
+ 一致——远程机器上的 dev server 日志与按键交互照常可用。
58
+
59
+ ## SSH 连接(方案 C)
60
+
61
+ 0.2.0 起标签栏「+」变为一键菜单,除本地终端外还能开 **SSH 标签页**:宿主
62
+ 半体用 `ssh2` 原生建立连接并打开 shell channel(不经过本地 ssh 进程,也
63
+ 不占 node-pty),包装成与本地 PTY 完全一致的会话对象——输入、resize、
64
+ 关闭、输出缓冲、背压与 agent 工具全部复用同一套调度。
65
+
66
+ - **「+」菜单三个入口**:本地终端 / **SSH 连接簿**(配置里保存过的条目,
67
+ 显示 `user@host[:port] · auth`)/ **SSH 连接…**(表单手填 host / port /
68
+ username / auth,连接前可勾选保存);
69
+ - **连接簿**:SSH 连接对话框勾选「保存到连接簿」即存为条目(同名覆盖,
70
+ 名称留空用主机名);也可在 设置 → 插件 → 终端面板 卡片维护(列表 +
71
+ 删除,随「保存」一并写入配置);
72
+ - **认证方式(auth)三选一**:
73
+ - `agent`(默认)——走 ssh-agent(`SSH_AUTH_SOCK`),凭证不落盘,最推荐;
74
+ - `key`——`keyPath` 私钥文件(`~` 开头可省略 home),`passphrase` 可选;
75
+ - `password`——密码认证,同时挂 keyboard-interactive(不少服务端只开这个);
76
+ - **密码 / 口令支持 `env:VAR`**:`password` / `passphrase` 填 `env:MY_SECRET`
77
+ 时从宿主进程环境变量取值(配合 dsh-env-manager 插件托管密钥,避免明文
78
+ 写进 settings 文件);
79
+ - **端口**:默认 22,非 22 端口在 target 里显示为 `user@host:port`;
80
+ - **标签与状态**:SSH 标签标题用连接名或 `user@host`(本地标签是
81
+ 「终端 N」);连接中先回显灰字 `Connecting user@host …`,就绪后状态栏
82
+ 显示 `SSH user@host 已连接`;连接失败(连接超时 / 认证被拒 / 主机
83
+ 不可达)以 `error` 帧带回原因,标签规格已随标签保存,点终端区域可按
84
+ 原规格重开;
85
+ - **计入 `maxSessions` 并发上限**;关断与本地会话一致:标签 ✕ / `kill`
86
+ 帧关闭 ssh2 channel,`exit` 帧照常带回退出码 / 信号。
87
+
44
88
  ## 配置(设置 → 插件 → 终端面板,保存即热生效)
45
89
 
46
90
  | 项 | 默认 | 说明 |
@@ -52,16 +96,18 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
52
96
  | `term` | `xterm-256color` | TERM 值 |
53
97
  | `colorTerm` | `truecolor` | COLORTERM 值 |
54
98
  | `cwd` | 宿主启动目录 | 兜底工作目录(客户端当前会话 cwd 优先) |
99
+ | `sshHosts` | `[]` | SSH 连接簿(面板「+」菜单可选):条目 `{name, host, port=22, username, auth=agent\|key\|password, keyPath, passphrase, password}`;保存时整体替换、同名覆盖;`password` / `passphrase` 支持 `env:VAR` 引用,避免明文入库 |
55
100
 
56
101
  ## 帧协议(/api/dsh-tty/ws,JSON 文本帧;v2 支持单连接多会话)
57
102
 
58
103
  | 方向 | 帧 | 说明 |
59
104
  | --- | --- | --- |
60
105
  | C→S | `{t:'spawn', sid?, cols?, rows?, cwd?}` | 创建会话;sid 缺省由宿主生成,cwd 缺省用配置兜底 |
106
+ | C→S | `{t:'ssh', sid?, cols?, rows?, name? \| host, username, …}` | 创建 SSH 会话(ssh2 原生);`name` 引用连接簿条目作基底,内联 `host/port/username/auth/keyPath/passphrase/password` 可逐项覆盖 |
61
107
  | C→S | `{t:'input', sid?, d}` | 按键/粘贴数据 |
62
108
  | C→S | `{t:'resize', sid?, cols, rows}` | 面板尺寸变化 |
63
109
  | C→S | `{t:'kill', sid?}` | 关闭会话 |
64
- | S→C | `{t:'ready', sid, pid}` | 会话就绪 |
110
+ | S→C | `{t:'ready', sid, pid, kind, target?}` | 会话就绪;`kind:'local'` 带 pid,`kind:'ssh'` 时 pid=null、target=user@host[:port] |
65
111
  | S→C | `{t:'data', sid, d}` | 终端输出(utf8 文本) |
66
112
  | S→C | `{t:'exit', sid, code, signal}` | PTY 退出事实(恰好一次) |
67
113
  | S→C | `{t:'error', sid?, m}` | 错误 |
@@ -79,6 +125,7 @@ pnpm --filter @hyzyn/dsh-tty probe # M0 探针:PTY 原语验证(需
79
125
  pnpm --filter @hyzyn/dsh-tty integration # 集成测试:真实插件 × 真实 DSH 服务组合
80
126
  pnpm --filter @hyzyn/dsh-tty live # 对运行中的 dsh web 做存活冒烟
81
127
  pnpm --filter @hyzyn/dsh-tty tui # TUI 冒烟:vim/nano 全屏渲染
128
+ pnpm --filter @hyzyn/dsh-tty ssh-smoke # SSH 冒烟:内存 SSH server(ssh2.Server)× 真实 spawnSsh 端到端(需先 build)
82
129
  ```
83
130
 
84
131
  浏览器半体源码在 `client-src/index.js`,构建产物 `client.js`(含 xterm 内核)。
@@ -100,6 +147,17 @@ pnpm --filter @hyzyn/dsh-tty tui # TUI 冒烟:vim/nano 全屏渲染
100
147
  - 浏览器半体依赖官方 `dsh-web-app` 的侧边栏结构(`[data-pane="sidebar"]`),
101
148
  非官方 Web GUI 可能不显示入口。
102
149
  - 连接断开(如宿主重启)时所有会话结束,需重新连接后重开标签。
150
+ - **SSH host key 当前为 accept-and-log**:不做 known_hosts 钉扎,每次连接
151
+ 记录 sha256 指纹后无条件放行,等同于手敲
152
+ `ssh -o StrictHostKeyChecking=no`(而非 accept-new)——首次连接不询问、
153
+ 主机指纹变更不告警,存在 MITM(中间人)冒充风险;loopback 围栏保证只有
154
+ 本机 Web GUI 能发起连接,仅作为缓解,known_hosts 钉扎留作后续项。
155
+ - **SSH 密码 / 口令建议 `env:VAR` 引用**:连接簿随 settings 文件落盘,
156
+ `password` / `passphrase` 明文入库有泄露面;建议 `env:VAR` +
157
+ dsh-env-manager 托管,或直接用 `agent` 认证(凭证不落盘)。
158
+ - **SSH 会话没有本地 pid**:ssh2 shell channel 不是本机进程,`ready.pid`
159
+ 为 `null`、`tty_list` 显示 `target` 而非 pid,本机 `ps` / `kill` 对远程
160
+ 进程无效——关闭请用标签 ✕ 或 `kill` 帧(关闭的是 ssh2 channel)。
103
161
 
104
162
  ## 工作原理
105
163
 
@@ -111,13 +169,21 @@ pnpm --filter @hyzyn/dsh-tty tui # TUI 冒烟:vim/nano 全屏渲染
111
169
  └─ WebSocket ──→ /api/dsh-tty/ws (webServer.registerUpgrade)
112
170
  │
113
171
  宿主半体 (src/index.ts)
114
- ├─ 连接内会话表(sid → PTY,单连接多会话)
115
- ├─ SessionManager(maxSessions 上限,热调整)
116
- ├─ ctx.get('subprocess').spawnTerminal({ argv: shell -c 包装层, cwd })
117
- └─ 帧协议:spawn/input/resize/kill ↔ ready/data/exit/error + 背压
172
+ ├─ 连接内会话表(sid → 本地 PTY / SSH channel,单连接多会话)
173
+ ├─ SessionManager(maxSessions 上限,热调整;SSH 会话同表调度)
174
+ ├─ 本地路径:ctx.get('subprocess').spawnTerminal({ argv: shell -c 包装层, cwd })
175
+ └─ 帧协议:spawn|ssh / input / resize / kill ↔ ready/data/exit/error + 背压
176
+
177
+ SSH 路径 (src/ssh.ts,方案 C)
178
+ └─ {t:'ssh'} → spawnSsh:ssh2 Client 原生连接(agent / key / password,
179
+ password·passphrase 支持 env:VAR 取密),开 shell channel 包装成与
180
+ PTY 同形状的 TermHandle(pid=null,kind='ssh',target=user@host[:port]),
181
+ 背压一并透传到 channel —— 之后与本地 PTY 无差别调度
118
182
  ```
119
183
 
120
184
  M0 探针、集成测试(12 项)与真实实例冒烟(live / TUI)在真实 DSH 服务组合
121
185
  上验证过:TERM 注入、resize 透传、sid 冲突、并发上限、loopback 围栏、
122
186
  多会话数据隔离、cwd 跟随与校验、配置热生效(settings/updated)、
123
- kill→exit 全链路。
187
+ kill→exit 全链路。SSH 路径由 `ssh-smoke`(内存 SSH server × 真实
188
+ `spawnSsh`)验证:password 认证建链与 prompt、命令往返、pty-req 初始
189
+ 尺寸与 resize(window-change)、terminate / exit-status 全链路。