dsh-ssh-vault 0.0.0-stage → 2.0.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.
@@ -0,0 +1,352 @@
1
+ # 变更记录
2
+
3
+ [English](CHANGELOG.md) | **简体中文**
4
+
5
+ 本文件记录所有值得注意的变更,格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
6
+ 版本号遵循[语义化版本](https://semver.org/lang/zh-CN/)。
7
+
8
+ ## [Unreleased]
9
+
10
+ 无待发布内容。
11
+
12
+ ## [2.0.0] - 2026-10-25
13
+
14
+ ### 移除
15
+
16
+ - **Linux 与 macOS 支持被移除——既在声明上,也在代码里。** 保险库数据密钥依赖 Windows DPAPI,
17
+ `SSH_ASKPASS` 辅助程序是 Windows 的 `.cmd`;此前的代码在非 Windows 上会静默把数据密钥以明文
18
+ 写进 `key.json`。那是安全降级,不是「支持」。现在包声明 `os: ["win32"]`,其它平台上安装会被
19
+ 拒绝;`apply()` 与 `Vault` 构造函数也会拒绝运行。
20
+
21
+ ### 变更
22
+
23
+ - **所有地方都写明仅 Windows**:`package.json` 的 description 与 `meta`、`locale/{en,zh}.json`、
24
+ 市场条目,以及中英 README(简介、环境要求、已知限制、FAQ)。
25
+ - 新增 `lib/platform.js`,集中维护支持平台列表、平台显示名与拒绝加载的错误文案;
26
+ `test/platform.mjs` 把代码声明与 `package.json` 的 `os` 字段钉在一起。
27
+ - `test/host-apply.mjs` 在非 Windows 主机上运行时会覆盖「拒绝加载」路径。
28
+ - 为信任栅栏补了回归:设置接口测试覆盖 401/403 响应体与 no-store 头、拒绝发生在方法与请求体
29
+ 处理**之前**、被拒请求绝不写入保险库,以及集成接线确实把宿主的 `connection` 服务传了进来
30
+ (`test/vault-api.mjs`、`test/host-apply.mjs`)。全套 321 项。
31
+ - 市场条目的 tarball 指向 v2.0.0。
32
+
33
+ ### 修复
34
+
35
+ - **README 曾承诺 Linux/macOS,而实现只会在那里降级。** 这次按诚实的方向消除文档与代码的不一致:
36
+ 任何平台上都不存在明文回退,不受支持的平台在安装与加载两个阶段都会明确失败。
37
+ - **设置接口现在会先过 Host Connection 的信任栅栏。** 每个 `/ssh-vault/*` 请求都会先调用
38
+ `ctx.connection.requestRejection(req)`——与 `/api` 相同的 Host/Origin 校验与浏览器登录
39
+ 认证——之后才会看方法、请求体或保险库。被拒绝的请求得到 `401`/`403`,既读不到主机数据、
40
+ 触发不了探测,也无法探测动作是否存在。正如评审指出的,保管凭据的接口不该只依赖外层服务器。
41
+ - **Windows 上对同一主机重复上传私钥不再失败。** 上一次上传的副本已被 icacls 收紧成
42
+ 当前用户只读的 ACL,连覆盖写入所需的权限也被一并剥夺。保存路径现在会在重写前先恢复
43
+ 该文件的 ACL(Windows 上 `icacls /reset`,其余平台 chmod 0600),第二次上传即可落盘。
44
+
45
+ ## [1.15.0] - 2026-10-24
46
+
47
+ ### 新增
48
+
49
+ - **插件跟随 DSH 的界面语言。** DSH 切成英文后,设置页、`/` 菜单项、对话内的 SSH 卡片、
50
+ `/hosts` 结果、连接诊断与设置接口错误都会变成英文;切回中文立即生效,不需要刷新页面
51
+ 或重启。
52
+ - Client 半边向宿主的 locale 服务注册 `ssh-vault` 字典,slot 渲染的文案随当前语言刷新。
53
+ - Host 半边从 Host 设置文档读取 `locale.preference`(就是语言选择器写入的那个值),并
54
+ 跟随 `settings/document-updated`;只有在没有显式偏好时才用请求的 `Accept-Language`。
55
+ - 面向模型的定义——工具名、描述与参数 schema——保持英文:它们属于模型接口而不是界面。
56
+ - 新增 `test/i18n.mjs`、`test/host-apply.mjs`、`test/client-smoke.mjs`:分别覆盖字典完整性
57
+ (占位符一致、英文条目没有漏译)、Host 激活与切语言路径、Client 槽位与字典接线。
58
+
59
+ ### 变更
60
+
61
+ - Host 侧的文案集中到 `lib/i18n.js` 与 `lib/locales/ssh.js`;中文值与改动前的字符串逐字一致,
62
+ 所以既有行为与测试不受影响。
63
+ - **`/hosts` 只在界面语言切换时重新注册。** 命令的 `description` 与 `input.hint` 是注册时
64
+ 捕获的静态字符串;定时器绝不碰命令定义(历史上正是它让输入框在打字途中失去命令上下文),
65
+ 而切语言是设置页里的主动操作、当时没有正在输入的命令,所以在那一刻替换定义是安全的。
66
+ - `/hosts` 的输入提示现在写明「再按一次回车即列出主机」,把菜单选中后的两步行为讲清楚。
67
+
68
+ ### 修复
69
+
70
+ - **无参 `/hosts` 不再看起来像「回车没反应」。** 体检缓存为空时,命令原先按 15 秒设置超时
71
+ 逐台串行探测——五台不可达主机就是最多 75 秒没有任何可见结果,结果还会迟到地落在用户
72
+ 随后发出的消息上方。现在列表形态改为并行探测、总预算 4 秒。
73
+ - **首条消息之前也能看到 `/hosts` 结果。** DSH 把「整段记录只有命令行」的会话判定为非活跃
74
+ 视图,于是全新会话里命令虽然执行并记下了结果,界面却什么都不渲染;卡片要等发出第一条
75
+ 消息后才出现在新消息上方。插件现在会在会话仍空白时把这些命令节点(含运行中状态)镜像到
76
+ 输入框上方的 dock,对话区有真实内容后自动让位。
77
+
78
+ ## [1.14.2] - 2026-10-19
79
+
80
+ ### 修复
81
+
82
+ - **PowerShell 的错误输出恢复为纯文本。** -OutputFormat Text 只作用于 stdout:
83
+ powershell.exe 仍会把 error 流序列化成 CLIXML 写进 stderr,并把整段脚本当作错误
84
+ 上下文回显,真正的信息被 XML 埋掉。现在脚本内部就把 error 流并入 stdout,并把每个
85
+ ErrorRecord 压成一行 [error] …,Out-String -Stream 保留对象与表格输出。
86
+
87
+ ## [1.14.1] - 2026-10-19
88
+
89
+ ### 修复
90
+
91
+ - **PowerShell 不再用 CLIXML 灌满 stderr。** 脚本改用 `-EncodedCommand` 之后,PowerShell
92
+ 把进度流序列化成 CLIXML 写进 stderr,真正的错误被一整段 XML 淹没。现在脚本开头设置
93
+ `\Continue = 'SilentlyContinue'`,调用时加 -OutputFormat Text。
94
+
95
+ ## [1.14.0] - 2026-10-19
96
+
97
+ ### 修复
98
+
99
+ - **多行 PowerShell 不再静默失败。** `-Command -` 按行读取标准输入,here-string(`@'…'@`)
100
+ 与 `foreach (…) {…}` 这类多行块会在解析途中被拆散;拆散后的语法错误不会以非零退出码
101
+ 回传,于是调用方被告知命令"执行了",实际什么都没发生。PowerShell 目标现在通过
102
+ `-EncodedCommand`(Base64 UTF-16LE)一次传入完整脚本、一次解析;脚本开头固定
103
+ `[Console]::OutputEncoding`,非 ASCII 输出不会变成乱码。
104
+ - **`ssh_transfer` 不再把连接中断误判成路径问题。** 传输因连接重置而失败时,路径提示仍会
105
+ 弹出,把排查引向外层。
106
+ - **账户锁定成为被点名的原因。** Windows OpenSSH 对已锁定账户是直接断开连接,而不是回
107
+ `Permission denied`,所以原先的"端口通、banner 正常、就是连不上"只指向防火墙。现在会
108
+ 把账户锁定列为可能原因,并给出检查方法(Windows `net user`,Linux `faillock`)。
109
+
110
+ ### 新增
111
+
112
+ - **同一主机的连接串行化。** 每次调用都新建 SSH 连接,短时间一批——尤其并发 `scp`——会触发
113
+ sshd 的连接数限制,并累积失败认证计数把账户推向锁定。后台任务不进这条队列,否则一个
114
+ 长任务会把该主机上其他调用全部堵死。
115
+ - `ssh_transfer` 下载前自动创建本地父目录。
116
+ - 执行会修改账户状态或锁定策略的命令前给出警告——管理账号被锁会连密钥登录一起断掉。
117
+
118
+ ### 变更
119
+
120
+ - `shellArgv` 接收脚本内容,PowerShell 目标返回 `-EncodedCommand`;POSIX 与 `cmd` 仍走 stdin。
121
+
122
+ ## [1.13.0] - 2026-10-17
123
+
124
+ ### 新增
125
+
126
+ - **`/hosts` 区分命令与自然语言。** 分号后读起来像自然语言的文字不再被当成 shell 执行;
127
+ 命令会停下并把这句话交还给对话,因为命令的产出不会回流给模型。
128
+ - `scripts/dhcp-reserve-kali.ps1`:把虚拟机的 MAC 固定到指定地址的幂等脚本,改写 VMware
129
+ 的 DHCP 配置后重启服务。
130
+
131
+ ### 变更
132
+
133
+ - **文档重写。** 两份 README 从「实现过程叙述」改为参考手册,加入徽章、Mermaid 数据流图,
134
+ 以及架构、扩展点、生命周期、已知限制与安全边界等章节。
135
+ - **所有顶层文档改为英文为主。** `CHANGELOG`、`CONTRIBUTING`、`SECURITY` 原先把中文放在
136
+ 不带后缀的 `.md` 里、英文放在 `.en.md` 里;现在三者都跟随 README 的约定——不带后缀的是
137
+ 英文,译文用 `.zh-CN` 后缀。
138
+
139
+ ## [1.12.3] - 2026-10-17
140
+
141
+ ### 修复
142
+
143
+ - `/hosts` 卡片头部的段名居中,不再和右侧的「有失败」徽章对齐。
144
+
145
+ ## [1.12.2] - 2026-10-17
146
+
147
+ ### 修复
148
+
149
+ - **两张 `/hosts` 卡片各自亮灯。** 原先两张卡读同一个取自整条命令的成败标志,导致主机
150
+ 连通但命令失败时,连接状态卡也被染红。
151
+
152
+ ## [1.12.1] - 2026-10-17
153
+
154
+ ### 新增
155
+
156
+ - `/hosts` 的输出拆成两张卡片:连接状态与命令执行。
157
+
158
+ ## [1.12.0] - 2026-10-17
159
+
160
+ ### 新增
161
+
162
+ - **分号后的文字会在每台可达主机上执行。** `/hosts kali; apt update` 先探测 kali,再在
163
+ 它上面执行该命令。以 `sudo` 开头的命令会先从保险库预置 sudo 缓存,否则非交互会话会
164
+ 一直等密码直到超时。
165
+
166
+ ## [1.11.1] - 2026-10-17
167
+
168
+ ### 修复
169
+
170
+ - 全角逗号 `,` 与半角逗号一样可以分隔主机别名。
171
+
172
+ ## [1.11.0] - 2026-10-17
173
+
174
+ ### 新增
175
+
176
+ - **可以在设置表单里给连接改名。** 凭据、私钥路径、备注与标签随之搬迁。宿主先写新记录
177
+ 再删旧记录,中途失败最坏是留下一条副本,而不是配置丢失。
178
+
179
+ ## [1.10.1] - 2026-10-17
180
+
181
+ ### 变更
182
+
183
+ - `/hosts` 输出回归纯文本,由卡片头部的一个圆点承担状态。emoji 色点重复表达了同一个
184
+ 信息,也比圆点难看。
185
+
186
+ ## [1.10.0] - 2026-10-17
187
+
188
+ ### 新增
189
+
190
+ - **`/hosts` 有了自己的终端行**,接管 `conversation.chat.commandview` 槽位。通用命令行卡片
191
+ 会把结果塞进无样式的 `<pre>` 并默认折叠。
192
+ - 全角分号 `;` 与半角分号一样可以结束参数列表。
193
+
194
+ ## [1.9.6] - 2026-10-17
195
+
196
+ ### 变更
197
+
198
+ - `/hosts` 输出用彩色圆点替代勾、叉、问号——后者读起来像判决而不是状态。
199
+
200
+ ## [1.9.5] - 2026-10-17
201
+
202
+ ### 修复
203
+
204
+ - **参数按「逗号分隔、分号结束」解析。** 原先按空格拆分,会把别名之后的每个词都当成主机
205
+ 去查,于是一句备注产生多次查找失败,并重复同一条错误。
206
+ - 未知别名先对保险库查一遍再单独报告,不再混进真正的连接失败里。
207
+
208
+ ## [1.9.4] - 2026-10-17
209
+
210
+ ### 变更
211
+
212
+ - **命令由 `/ssh` 改名为 `/hosts`。** 输入框是 Lexical 编辑器,普通文本节点受浏览器拼写
213
+ 检查约束;`ssh` 不是词典词会被标红,`hosts` 是。
214
+
215
+ ## [1.9.3] - 2026-10-17
216
+
217
+ ### 修复
218
+
219
+ - **命令只注册一次,不再重注册。** 重注册会销毁当前定义,而输入框在用户打字期间正持有
220
+ 它——定义被换掉会让输入失效(退格失灵),只能重启界面恢复。
221
+
222
+ ## [1.9.2] - 2026-10-17
223
+
224
+ ### 修复
225
+
226
+ - **状态圆点跟随每一次测试。** 卡片上的「测试连接」按钮原先只更新下方结果区、从不更新
227
+ 圆点,所以测试成功也留着红点;编辑连接后也不会重测,因为自动体检的触发条件只有别名
228
+ 列表,而改地址并不会改变别名。
229
+
230
+ ## [1.9.1] - 2026-10-17
231
+
232
+ ### 修复
233
+
234
+ - **设置页探测超时改为 15 秒(原 8 秒)。** 探测是真的建立 SSH 连接,主机活着但 22 端口
235
+ 没有监听时,TCP 连接会把整个窗口耗光。
236
+ - 超时按超时报告,点名两种可能(端口不通、地址已变),而不是退化成一句 `(no output)`
237
+ 让人以为插件坏了。
238
+
239
+ ## [1.9.0] - 2026-10-17
240
+
241
+ ### 新增
242
+
243
+ - `/ssh` 的输入提示列出上一次体检中在线的主机;不带参数执行时按在线/离线分组打印主机。
244
+
245
+ ## [1.8.0] - 2026-10-17
246
+
247
+ ### 新增
248
+
249
+ - **卡片一出现就各自体检一次**,并显示状态圆点:灰=未检测、黄=检测中、绿=连通、红=不通。
250
+ 体检串行执行,避免一批并发 SSH 连接一起挤在一台不通的主机上。
251
+
252
+ ## [1.7.1] - 2026-10-17
253
+
254
+ ### 修复
255
+
256
+ - 终端标题行显示退出状态。对这些工具而言非零退出码属于正常返回而非错误,所以失败的
257
+ 命令与成功的命令原先看起来一模一样。
258
+
259
+ ## [1.7.0] - 2026-10-17
260
+
261
+ ### 新增
262
+
263
+ - **SSH 工具调用在对话里渲染成可滚动终端**,注册在 `tool.call.toolview` 上,覆盖
264
+ `ssh_run`、`ssh_probe`、`ssh_transfer`。这三个 key 原本都未被占用,不覆盖任何内置 UI。
265
+
266
+ ## [1.6.9] - 2026-10-17
267
+
268
+ ### 修复
269
+
270
+ - 工具输出退回到 `console` 围栏代码块。`presentCall` / `presentResult` 这组「终端卡片」
271
+ 钩子在整棵安装树里没有任何消费方,因此不能作为唯一的呈现路径。
272
+
273
+ ## [1.6.8] - 2026-10-17
274
+
275
+ ### 新增
276
+
277
+ - 三个终端类工具注册了 `presentCall` / `presentResult` 钩子。参见 1.6.9 与 1.7.0:这组钩子
278
+ 在当前 DSH 版本没有消费方,真正的呈现由槽位实现。
279
+
280
+ ## [1.6.7] - 2026-10-17
281
+
282
+ ### 新增
283
+
284
+ - **上传的私钥做了限制与预检。** 浏览器在真正上传之前先读一遍文件,拒绝公钥、PuTTY 的
285
+ `.ppk`、缺少 PEM 头的文件,以及大到不像私钥的文件。宿主侧要求真正的 PEM 头,而不是
286
+ 仅包含 `PRIVATE KEY` 这几个词。
287
+ - 密钥表单增加口令字段,用于本身带密码的私钥。
288
+
289
+ ## [1.6.6] - 2026-10-17
290
+
291
+ ### 修复
292
+
293
+ - 「用资源管理器打开」的结果显示在按钮旁边。原先失败信息只出现在页面顶部,按下去像是
294
+ 没有任何反应。
295
+
296
+ ## [1.6.5] - 2026-10-17
297
+
298
+ ### 修复
299
+
300
+ - 编辑表单在对应卡片下方就地展开,而不是出现在页面顶部把正在编辑的卡片挤出视野。
301
+
302
+ ## [1.6.4] - 2026-10-17
303
+
304
+ ### 新增
305
+
306
+ - 保险库路径独占一行,并带一个「用资源管理器打开」按钮,背后是 `openVault` 路由——它
307
+ 不接受任何路径参数,只能打开插件自己的保险库目录。
308
+
309
+ ## [1.6.3] - 2026-10-17
310
+
311
+ ### 修复
312
+
313
+ - 保险库元信息行不再在分隔用的全角空格处折行。
314
+ - 插件版本改为每次读取而非缓存,避免只改客户端时版本号停留在上一版。
315
+
316
+ ## [1.6.2] - 2026-10-17
317
+
318
+ ### 修复
319
+
320
+ - **设置路由注册为 `/ssh-vault`,不带尾斜杠。** `kind: 'prefix'` 按「路径本身」或「路径 + `/`」
321
+ 匹配,注册成 `/ssh-vault/` 时 `/ssh-vault/list` 两者都不命中,所有请求落到 fallback。
322
+
323
+ ## [1.6.1] - 2026-10-17
324
+
325
+ ### 修复
326
+
327
+ - 副按钮改用宿主自己的按钮 token。此前凭 token 名字猜语义,在深色主题下产生了浅底浅字。
328
+
329
+ ## [1.6.0] - 2026-10-17
330
+
331
+ ### 新增
332
+
333
+ - **设置界面的连接管理**:列出、添加、编辑、删除主机,支持口令或私钥认证与一键测试连接。
334
+ 在此输入的口令经同源 POST body 提交,不进入会话日志。
335
+ - **`/ssh` 命令**:列出主机,或按别名探测一台或多台。
336
+ - `test/vault-api.mjs`,钉住设置接口的校验、脱敏与响应形状。
337
+
338
+ ## [1.5.0] - 2026-10-16
339
+
340
+ ### 新增
341
+
342
+ - 加密 SSH 保险库:AES-256-GCM,数据密钥在 Windows 上由 DPAPI 保护,其它平台由主口令经
343
+ scrypt 派生。
344
+ - 模型面向工具 `ssh_hosts`、`ssh_run`、`ssh_transfer`、`ssh_probe`,全部只用别名寻址主机。
345
+ - 比一次工具调用活得更久的命令支持后台任务。
346
+ - 保险库目录内私有的 `known_hosts`,启用 `HashKnownHosts`,从不触碰用户的 `~/.ssh`。
347
+ - 通过 `scripts/set-secret.mjs` 离线录入口令。
348
+
349
+ ### 安全
350
+
351
+ - 口令只经 `SSH_ASKPASS` 传给本地 ssh,远端 `sudo` 经 stdin 预置;两者都不出现在命令行或
352
+ 进程列表中。
@@ -0,0 +1,179 @@
1
+ # Contributing
2
+
3
+ **English** | [简体中文](CONTRIBUTING.zh-CN.md)
4
+
5
+ This plugin **stores other people's passwords** and **runs commands on their machines**. Its bar is
6
+ therefore higher than a typical utility: the red lines in section 1 are not style preferences — breaking
7
+ one means a real leak or a real incident.
8
+
9
+ ## 1. Red lines (a violation is grounds for rejection)
10
+
11
+ ### 1.1 Never commit credentials, private keys, or real environment details
12
+
13
+ Do not commit: private keys, passwords, API keys, anything under `%DSH_HOME%`, real private addresses, real
14
+ host names, real account names, or session logs.
15
+
16
+ Tests that need a real host **always** take it from the environment; no literal values in the source:
17
+
18
+ ```js
19
+ // Correct
20
+ const KALI = { host: env.LIVE_KALI_HOST, user: env.LIVE_KALI_USER, pass: env.LIVE_KALI_PASS }
21
+
22
+ // Wrong
23
+ const KALI = { host: '198.51.100.7', user: 'root', pass: '<password>' }
24
+ ```
25
+
26
+ Examples, fixtures, and documentation use **documentation addresses only**: `192.0.2.0/24`,
27
+ `198.51.100.0/24`, `203.0.113.0/24`.
28
+
29
+ Before you press commit:
30
+
31
+ ```powershell
32
+ git status --short # no id_rsa / id_rsa.pub / dist / .env
33
+ git diff --cached # read it, especially for addresses, accounts, passwords
34
+ ```
35
+
36
+ `.gitignore` covers the common cases, **but that is not a guarantee** — it will not stop a new file named
37
+ `config.local.js`.
38
+
39
+ ### 1.2 A password must never reach a command line, a log, or the conversation
40
+
41
+ This is the core design constraint of the project. Every change must preserve:
42
+
43
+ | Location | Requirement |
44
+ | --- | --- |
45
+ | Local `ssh` command line | The password may only travel through the `SSH_ASKPASS` environment variable |
46
+ | Remote command line and `ps` | The password may only travel on standard input |
47
+ | Session log | No tool parameter may contain a password; only switches such as `sudo: true` |
48
+ | Disk | Ciphertext only (AES-256-GCM); plaintext is allowed in memory only |
49
+
50
+ After changing an execution path, the "password does not appear in the remote process list" case in
51
+ `test/live.mjs` must still pass.
52
+
53
+ ### 1.3 Stay dependency-free
54
+
55
+ Do not add npm dependencies. The plugin drives the system OpenSSH client (`ssh` / `scp`) and Node built-ins
56
+ only. `package.json` must not gain a `dependencies` block.
57
+
58
+ ### 1.4 Do not touch the user's profile or vault
59
+
60
+ At runtime the plugin reads and writes only its own vault directory (`%DSH_HOME%\ssh-vault`). Do not write
61
+ `%DSH_HOME%\.credentials.yaml`, the profile's `package.json` or `cordis.patch.yml`, and do not touch the
62
+ user's `~/.ssh`.
63
+
64
+ ## 2. Development environment
65
+
66
+ - Node.js >= 20 (see `engines` in `package.json`)
67
+ - Windows 10 / 11 with the OpenSSH Client enabled (`ssh -V` prints a version). **The plugin is Windows-only by
68
+ design** — DPAPI protects the data key and the askpass helper is a Windows `.cmd`; `package.json` declares
69
+ `os: ["win32"]` and the runtime refuses to load elsewhere. Do not reintroduce a non-Windows code path that
70
+ stores the key as plaintext.
71
+ - **No `npm install` needed** — the project has no dependencies
72
+
73
+ ## 3. Tests
74
+
75
+ ### Required before every commit
76
+
77
+ ```powershell
78
+ npm test
79
+ ```
80
+
81
+ It runs three **host-free** suites in order, and CI runs exactly the same set:
82
+
83
+ ```powershell
84
+ node test/diagnose.mjs # shell translation, octal decoding, failure attribution
85
+ node test/unit.mjs # argument construction, timeout/cancel output retention, wait/detach scripts
86
+ node test/background.mjs # background job registration contract
87
+ ```
88
+
89
+ ### When a real host is involved
90
+
91
+ ```powershell
92
+ $env:LIVE_KALI_HOST = '<host>'
93
+ $env:LIVE_KALI_USER = '<user>'
94
+ $env:LIVE_KALI_PASS = '<password>'
95
+ $env:LIVE_WIN_HOST = '<host>'
96
+ $env:LIVE_WIN_USER = '<user>'
97
+ $env:LIVE_WIN_KEY = '<private key path>'
98
+ node test/live.mjs
99
+ node test/selftest.mjs
100
+ ```
101
+
102
+ ### Hard requirements
103
+
104
+ - Touching argument construction or attribution in `lib/ssh.js` → **must** add cases to `test/unit.mjs` /
105
+ `test/diagnose.mjs`
106
+ - Touching background jobs → **must** add cases to `test/background.mjs`
107
+ - Adding model-facing behaviour → at least one unit case to pin it
108
+ - **Never loosen an assertion to make a suite pass.** If a new case requires changing an old assertion,
109
+ explain in the pull request why the old assertion was wrong
110
+
111
+ ## 4. Code style
112
+
113
+ Formatting is enforced by [`.editorconfig`](.editorconfig). Conventions on top of it:
114
+
115
+ - ESM (`import` / `export`), `"type": "module"`
116
+ - 2-space indent, single quotes, **no trailing semicolons**, ~120 column width
117
+ - JSDoc (`@param` / `@returns`) on exported functions; **comments in Chinese, identifiers in English**
118
+ - Prefer pure functions: argument construction (`buildSshArgs`), script generation (`buildWaitScript`,
119
+ `buildDetachScript`) and attribution (`diagnose`) are side-effect free so they stay testable
120
+ - Error messages are written for the user: say **what happened** and **how to recover**, do not just re-throw
121
+ the underlying error
122
+
123
+ ## 5. Commit messages
124
+
125
+ [Conventional Commits](https://www.conventionalcommits.org/):
126
+
127
+ ```
128
+ <type>: <short description>
129
+
130
+ <optional body: why>
131
+ ```
132
+
133
+ `type` is one of `feat` / `fix` / `docs` / `refactor` / `test` / `chore`. Titles are English imperative
134
+ sentences; the body may be Chinese or English. **One commit does one thing.**
135
+
136
+ ## 6. Pull requests
137
+
138
+ 1. Open an issue first for behaviour changes, especially anything touching permissions, execution paths, or
139
+ the storage format
140
+ 2. Branch names: `feat/...`, `fix/...`, `docs/...`
141
+ 3. Say three things in the description: **what changed, why, and how you verified it**
142
+ 4. If any of the following applies, call it out explicitly:
143
+ - a new execution path (where the password travels)
144
+ - a vault format change (is a migration needed, what happens to existing data)
145
+ - a new tool parameter (does it carry sensitive data into the session log)
146
+ 5. `npm test` passes, and `git diff` contains no credentials
147
+
148
+ ## 7. Architectural constraints
149
+
150
+ Confirm you have not broken these before changing code:
151
+
152
+ - **Alias addressing**: the model sees aliases only; addresses and credentials come from the vault. Do not add
153
+ a parameter that makes the model pass a host name, account, or password.
154
+ - **An interrupted call is not a blind one**: every execution path must return the output it already received
155
+ when it times out or is cancelled.
156
+ - **Failures must name the layer**: local key problems, a public key that is not installed, a wrong password,
157
+ and a firewall drop must be distinguishable. "Wrong account or password" may only appear once the earlier
158
+ causes are excluded.
159
+ - **Long tasks must not hold a tool call**: prefer `detach` (remote detach) and `wait_for` (condition
160
+ waiting); do not write `sleep` polling into the implementation.
161
+ - **The session log is the only source of truth**: do not add custom session event types. Plugin state must
162
+ either be reconstructable from the log or live in the vault / a storage service.
163
+ - **Version your storage format**: changing the shape of `vault.json` / `key.json` requires a version bump
164
+ and the ability to read old data.
165
+
166
+ ## 8. Project layout
167
+
168
+ | Path | Responsibility |
169
+ | --- | --- |
170
+ | `index.js` | Plugin entry, the four model-facing tools, config parsing, credential resolution |
171
+ | `lib/vault.js` | Encrypted vault (AES-256-GCM + DPAPI / scrypt) |
172
+ | `lib/ssh.js` | OpenSSH driver: argument construction, askpass injection, attribution, wait/detach scripts |
173
+ | `lib/background.js` | `ctx.jobs` background task adapter |
174
+ | `scripts/set-secret.mjs` | Offline password entry (neither DSH nor the model is involved) |
175
+ | `test/` | Regression; the first three need no host, the rest need a real target |
176
+
177
+ ## 9. License
178
+
179
+ By contributing you agree to license your contribution under the [MIT license](LICENSE).