dsh-ssh-tunnel 1.0.0 → 1.0.2

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/CHANGELOG.md CHANGED
@@ -10,6 +10,62 @@ All notable changes to this project are documented in this file.
10
10
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
11
11
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
12
 
13
+ ## [1.0.2] - 2026-10-08
14
+
15
+ ### 新增
16
+
17
+ - **交互式输出读取采用 seq 游标协议。** 侧栏轮询接口 `shellRead` 的响应带 `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`:客户端携带上次读到的 `since`,宿主返回其后的新增输出;环形缓冲(512 KiB)淘汰后 `baseSeq` 前移且 `dropped: true`,客户端据此全量重取,避免缓冲滚动造成输出缺口。单次响应的 `chunk` 上限 256 KiB。
18
+ - **API 错误返回结构化错误码。** 错误响应体带 `code` 字段,并按语义映射 HTTP 状态:`bad_request` 400、`forbidden` 403、`not_found` 404、`conflict` 409、`gone` 410、`payload_too_large` 413;未映射的错误保持 500。
19
+ - **主机密钥确认提示有时效与上限。** 待确认提示 5 分钟过期,至多保留 50 条(超出时淘汰最旧),按项目隔离。
20
+
21
+ ### 变更
22
+
23
+ - **安装脚本以 `--profile` 为必填参数。** `scripts/install.sh` 与 `scripts/install.ps1`:目标 profile 必填且无默认值,缺失或不存在的 profile 报错并列出实存 profile(退出码 2)。`--fix-profile` / `-FixProfile` 为可选开关:仅在该开关下补写 profile `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude`(幂等)并清理 profile `cordis.patch.yml` 里旧版写入的手动挂载,写入后回读断言,失败回滚并以非零码退出。bash 试运行为 `--dry-run`,PowerShell 为 `-DryRun`。`PATH` 缺少 `dsh` 命令时的 `npx` 兜底先打印将执行的命令并要求确认(`DSH_INSTALL_YES=1` 跳过)。安装后读取 profile 的 `ignoredBuilds`,发现条目时打印 `allowBuilds` 豁免指引。`scripts/sync-to-dsh.sh` 支持 `--dry-run`。
24
+ - **兼容区间按预发布线拆段。** `engines.dsh` 与 `@deepseek-ai/dsh-client-locale` peer 声明为 `>=0.1.7-rc.2 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0`,0.1.7 线与 0.2 线各占一段。
25
+ - **exec 结果如实结算。** 返回值带 `exitKnown` / `code` / `signal` / `connectionDropped`:正常退出 `exitKnown: true` 且 `code` 为退出码;按信号终止时 `signal` 为信号名;连接被切断时 `code` 为 `null` 且 `connectionDropped: true`。
26
+ - **`max_bytes` 按字节钳制。** exec 与 `sftp_read_text` 的输出上限钳制在 1024–1048576 字节(默认 exec 262144、`sftp_read_text` 524288),输出按字节截断并在结果中置 `truncated: true`。
27
+ - **客户端注入清单补齐到达顺序边。** `dsh.client.inject` 增加 `dsh-better-sidebar`;`exports` 放行 `./cordis.patch.yml`;`files` 放行 `scripts/lib/*.cjs`(安装脚本共享逻辑)。
28
+ - **键盘交互式认证不支持。** 该类主机在连接、重连与 `SSHManager` 中被拒绝(`credential=unsupported`),提示改用密码或私钥。
29
+ - **文档更新。** README 安装章节重构(桌面版走 App 插件页、CLI 形态限自建 web/headless profile、`--profile` 占位符、allowBuilds 处置)、安全章节改为客观口径(本机回环、无鉴权 token、本机进程视为已授权用户)、补 seq 游标协议与超时/上限声明、0700/0600 加 POSIX 平台限定。
30
+
31
+ ### 安全
32
+
33
+ - **授权检查全程 fail-closed。** 建立连接前必须已有该项目授权;写类操作在发起前与结果回报前各复检一次;授权撤销即时生效——受影响的存活会话被关闭、在飞输出被丢弃并以 `not authorized` 失败、下载中的本地文件被删除。本地工作区根取不到时拒绝操作,可用环境变量 `DSH_SSH_TUNNEL_WORKSPACE_ROOT` 显式指定。
34
+
35
+ ### Added
36
+
37
+ - **Interactive output is read through a seq cursor protocol.** The sidebar polling API `shellRead` returns `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`: the client sends the last `since` it has seen and the host returns the newer output; ring-buffer eviction (512 KiB) moves `baseSeq` forward and sets `dropped: true`, telling the client to refetch in full, which prevents output gaps from buffer rollover. A single response caps `chunk` at 256 KiB.
38
+ - **API errors carry structured codes.** Error response bodies include a `code` field mapped to HTTP statuses by meaning: `bad_request` 400, `forbidden` 403, `not_found` 404, `conflict` 409, `gone` 410, `payload_too_large` 413; unmapped errors stay 500.
39
+ - **Host key confirmation prompts have a lifetime and a cap.** Pending prompts expire after 5 minutes, at most 50 are kept (oldest evicted beyond that), and they are isolated per project.
40
+
41
+ ### Changed
42
+
43
+ - **The install scripts require `--profile`.** `scripts/install.sh` and `scripts/install.ps1`: the target profile is required with no default; a missing or nonexistent profile fails with a list of the profiles that do exist (exit code 2). `--fix-profile` / `-FixProfile` is an optional switch: only with it does the script add the plugin to the profile's `pnpm-workspace.yaml` (`minimumReleaseAgeExclude`, idempotent) and remove the hand-written mount older versions put into the profile's `cordis.patch.yml`; writes are read back and asserted, and a failure rolls the change back and exits non-zero. The bash dry-run flag is `--dry-run`, the PowerShell one is `-DryRun`. When `dsh` is missing from `PATH`, the `npx` fallback prints the command it is about to execute and asks for confirmation (`DSH_INSTALL_YES=1` skips it). After installing, the script reads the profile's `ignoredBuilds` and prints an `allowBuilds` exemption recipe when entries are found. `scripts/sync-to-dsh.sh` supports `--dry-run`.
44
+ - **The compatibility range is split per prerelease line.** `engines.dsh` and the `@deepseek-ai/dsh-client-locale` peer declare `>=0.1.7-rc.2 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0` — the 0.1.7 line and the 0.2 line each get their own segment.
45
+ - **exec results are settled faithfully.** Results carry `exitKnown` / `code` / `signal` / `connectionDropped`: a normal exit sets `exitKnown: true` with the exit code in `code`; a signal death returns the signal name in `signal`; a cut connection returns `code: null` with `connectionDropped: true`.
46
+ - **`max_bytes` is clamped in bytes.** The output cap for exec and `sftp_read_text` is clamped to 1024–1048576 bytes (defaults: 262144 for exec, 524288 for `sftp_read_text`); output is truncated on byte boundaries and the result sets `truncated: true`.
47
+ - **The client inject list completes the arrival-order edge.** `dsh.client.inject` adds `dsh-better-sidebar`; `exports` admits `./cordis.patch.yml`; `files` admits `scripts/lib/*.cjs` (installer shared logic).
48
+ - **keyboard-interactive auth is not supported.** Such hosts are refused on connect, reconnect and in `SSHManager` (`credential=unsupported`) with a message to switch to password or private key.
49
+ - **Documentation updates.** The README install section is restructured (desktop installs go through the app plugin page, CLI forms target self-hosted web/headless profiles, `<profile>` placeholders, allowBuilds handling), the security section states the trust boundary objectively (local loopback, no authentication token, local processes treated as authorized users), the seq cursor protocol and timeout/limit declarations are added, and the 0700/0600 statements carry a POSIX platform qualifier.
50
+
51
+ ### Security
52
+
53
+ - **Authorization checks fail closed end to end.** Connecting requires an existing project grant; write operations re-check the grant once before being issued and once before the result is delivered; a revocation takes effect immediately — affected live sessions are closed, in-flight output is discarded and the call fails with `not authorized`, and a local file mid-download is deleted. Operations are refused when no local workspace root can be resolved; the environment variable `DSH_SSH_TUNNEL_WORKSPACE_ROOT` can set one explicitly.
54
+
55
+ ## [1.0.1] - 2026-10-04
56
+
57
+ ### 新增
58
+
59
+ - 插件有了自己的图标:包根 `icon.svg`(36×36,双主机 + 双轨脉冲管道),由 `package.json` 顶层 `icon` 字段声明并列入 `files`。插件列表里不再显示 DSH 的默认图形。
60
+ - 侧边栏标签图标改用同一张图:`lib/client.js` 不再自画 24 画板的铅笔图标,改为内联渲染 `icon.svg` 的双主机与双轨脉冲(按 36 画板绘制,再放大 1.3 倍以贴合侧边栏的视觉重量)。插件列表与侧边栏因此是同一张标。
61
+ - 插件列表里的显示名与描述有了中英两份(`locale/en.json`、`locale/zh.json` 的 `meta.title` 与 `meta.description`),中文名定为「SSH 隧道」;`package.json` 的 `exports` 与 `files` 相应放行 `locale/*.json`。此前该处回退成包名与英文 `description`,在中文界面里中英混排。
62
+
63
+ ### Added
64
+
65
+ - The plugin now ships its own icon: `icon.svg` at the package root (36×36, two hosts joined by a dual-rail pulse), declared through the top-level `icon` field in `package.json` and listed in `files`. The plugin list no longer falls back to the default DSH artwork.
66
+ - The sidebar tab icon now renders the same artwork as `icon.svg` instead of a separate 24-unit pencil glyph: `lib/client.js` draws the two hosts and the dual-rail pulse on the 36-unit canvas and scales them 1.3× to match the sidebar's visual weight. The plugin list and the sidebar therefore carry one mark.
67
+ - The plugin's display name and description in the plugin list now ship in both languages (`meta.title` and `meta.description` in `locale/en.json` and `locale/zh.json`), with the Chinese name settled as 「SSH 隧道」; `exports` and `files` in `package.json` admit `locale/*.json` accordingly. The list previously fell back to the package name and the English `description`, mixing languages inside a Chinese interface.
68
+
13
69
  ## [1.0.0] - 2026-09-28
14
70
 
15
71
  ### 变更
@@ -303,3 +359,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
303
359
  ### Added
304
360
 
305
361
  - Initial permanent plugin scaffold
362
+
363
+ [1.0.2]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v1.0.1...v1.0.2
364
+ [1.0.1]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v1.0.0...v1.0.1
365
+ [1.0.0]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.6...v1.0.0
366
+ [0.4.6]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.5...v0.4.6
367
+ [0.4.5]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.3...v0.4.5
368
+ [0.4.3]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.2...v0.4.3
369
+ [0.4.2]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.1...v0.4.2
370
+ [0.4.1]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.0...v0.4.1
371
+ [0.4.0]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.3.11...v0.4.0
372
+ [0.3.11]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.3.7...v0.3.11
373
+ [0.3.7]: https://github.com/OMSociety/dsh-ssh-tunnel/tree/v0.3.7
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  <p align="center"><strong>简体中文</strong> | <a href="README_en.md">English</a></p>
2
2
 
3
3
  <div align="center">
4
- <h1>dsh-ssh-tunnel</h1>
4
+ <img src="https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/docs/logo.png" alt="DSH SSH Tunnel" width="160">
5
+ <h1>DSH SSH Tunnel</h1>
5
6
  <p>DeepSeek Harness 的多机 SSH 工作台:主机库、按项目授权、右侧栏里的终端与双栏 SFTP。</p>
6
7
  <p>模型用 <strong>SSHManager</strong> 工具执行命令、传文件;你在侧栏管主机与授权。<strong>密钥不进模型上下文</strong>。</p>
7
8
 
8
9
  <p>
9
- <a href="https://github.com/OMSociety/dsh-ssh-tunnel/releases"><img src="https://img.shields.io/badge/version-1.0.0-4f6ef7" alt="Version"></a>
10
- <a href="https://github.com/deepseek-ai/dsh"><img src="https://img.shields.io/badge/DSH-%3E%3D0.1.7--rc.2_%3C0.3.0--0-4f6ef7" alt="DSH"></a>
10
+ <a href="https://github.com/OMSociety/dsh-ssh-tunnel/releases"><img src="https://img.shields.io/github/v/tag/OMSociety/dsh-ssh-tunnel?label=version&color=4f6ef7" alt="Version"></a>
11
+ <img src="https://img.shields.io/badge/DSH-%3E%3D0.1.7--rc.2%20%3C0.2.0--0%20%7C%7C%20%3E%3D0.2.0--rc.1%20%3C0.3.0--0-4f6ef7" alt="DSH">
11
12
  <a href="LICENSE"><img src="https://img.shields.io/github/license/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="License"></a>
12
13
  <a href="https://github.com/OMSociety/dsh-ssh-tunnel/stargazers"><img src="https://img.shields.io/github/stars/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Stars"></a>
13
14
  <a href="https://github.com/OMSociety/dsh-ssh-tunnel/issues"><img src="https://img.shields.io/github/issues/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Issues"></a>
@@ -16,11 +17,9 @@
16
17
  <a href="#这是什么">这是什么</a> • <a href="#核心特性">核心特性</a> • <a href="#快速开始">快速开始</a> • <a href="#侧栏">侧栏</a> • <a href="#模型工具">模型工具</a> • <a href="#安全">安全</a> • <a href="#开发">开发</a> • <a href="#许可证与作者">许可证与作者</a>
17
18
  </div>
18
19
 
19
- > **提示:**本仓库是 `dsh-ssh-tunnel` 的维护主线,在 [OMSociety 仓库](https://github.com/OMSociety/dsh-ssh-tunnel) 独立延续(2026-09-28 起脱离 fork 网络)。上游项目与代码作者是 [thirsty5034/dsh-ssh-tunnel](https://github.com/thirsty5034/dsh-ssh-tunnel)(MIT,见 [LICENSE](LICENSE))。修复与问题反馈都在本仓库处理。
20
-
21
20
  ## 这是什么
22
21
 
23
- **dsh-ssh-tunnel** 是 [DeepSeek Harness](https://github.com/deepseek-ai/dsh) 的社区插件,挂在右侧栏宿主 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 上:把多台 SSH 主机收进一个**主机库**,按项目授权,然后在中央面板里开**交互式终端**(xterm)或**双栏 SFTP**。
22
+ **DSH SSH Tunnel** 是 DeepSeek Harness 的社区插件,挂在右侧栏宿主 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 上:把多台 SSH 主机收进一个**主机库**,按项目授权,然后在中央面板里开**交互式终端**(xterm)或**双栏 SFTP**。
24
23
 
25
24
  它**不会**把全局 `fs` / `subprocess` 换成一个远程盘:远端操作只发生在你显式调用的 `SSHManager` 工具与面板里,本地文件与远端文件始终是两侧分明的东西。
26
25
 
@@ -47,29 +46,62 @@
47
46
 
48
47
  ## 快速开始
49
48
 
50
- **方式一:命令行直装(推荐)**
49
+ **桌面版(推荐路径)**
51
50
 
52
- ```powershell
53
- # 1) 先停掉 dsh web(运行中的服务会锁住依赖,装完再起)
54
- dsh plugin --profile web add "github:OMSociety/dsh-ssh-tunnel"
55
- # 2) 重新启动 dsh web
51
+ DeepSeek Harness 桌面版的插件由 App 统一管理:在 App 的**插件页**安装并升级本插件即可。桌面 profile 目录由 App 独占管理,请不要对它运行下面的 CLI 命令或安装脚本。
52
+
53
+ **CLI 安装(仅限自建 web / headless profile)**
54
+
55
+ 以下三种方式只针对自建的 web / headless profile。`<profile>` 处填你自己的 profile 名。
56
+
57
+ 方式一:从 npm 安装
58
+
59
+ ```sh
60
+ dsh plugin --profile <profile> add "dsh-ssh-tunnel@1.0.2"
61
+ ```
62
+
63
+ 包已发布到 npm,随包提供预构建产物,本地不需要构建步骤;换版本就把 `@1.0.2` 换成目标版本。
64
+
65
+ 方式二:从 GitHub 源安装
66
+
67
+ ```sh
68
+ dsh plugin --profile <profile> add "github:OMSociety/dsh-ssh-tunnel"
56
69
  ```
57
70
 
58
71
  想复现某次安装就钉住 ref:在仓库地址后加 `#<tag 或提交 sha>`。
59
72
 
60
- **方式二:一键脚本**
73
+ 方式三:一键脚本
61
74
 
62
75
  ```sh
63
- curl -fsSL https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.sh | bash
76
+ curl -fsSL https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.sh | bash -s -- --profile <profile>
64
77
  ```
65
78
 
66
79
  ```powershell
67
- irm https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.ps1 | iex
80
+ & ([scriptblock]::Create((irm https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.ps1))) -Profile <profile>
68
81
  ```
69
82
 
70
- 脚本除安装外还会把 profile 的 `minimumReleaseAgeExclude` 补上本插件、校验 `dsh.profile.bundles` 确实写入、清掉旧版手写在 profile `cordis.patch.yml` 里的挂载(先加 `--dry-run` 可只看计划不动手)。
83
+ 脚本参数(bash 与 PowerShell 一一对应):
84
+
85
+ | 说明 | bash | PowerShell |
86
+ |---|---|---|
87
+ | 安装版本(可选,缺省装最新发布版) | `[版本]`(首个位置参数) | `-Version <版本>` |
88
+ | **目标 profile(必填,无默认值)** | `--profile <名称>` | `-Profile <名称>` |
89
+ | 安装源(默认 `github`) | `--from github\|npm` | `-From github\|npm` |
90
+ | 修 profile(可选开关) | `--fix-profile` | `-FixProfile` |
91
+ | 重启 web(可选开关) | `--restart` | `-Restart` |
92
+ | 试运行(可选开关) | `--dry-run` | `-DryRun` |
93
+
94
+ - profile 缺失或不存在时脚本报错并列出实存 profile,退出码 2
95
+ - `--fix-profile` / `-FixProfile`:仅在该开关下,脚本才补写 profile `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude`(幂等)并清理 profile `cordis.patch.yml` 里旧版写入的手动挂载;写入后回读断言,失败回滚并以非零码退出。不带该开关时这两个文件保持原样
96
+ - `--restart` / `-Restart`:重启自托管 web 服务(按 pm2 进程名 `dsh-web`);桌面版请在 App 的插件页操作
97
+
98
+ > **提示**:两个脚本的试运行写法不同——bash 用 `--dry-run`(如 `bash scripts/install.sh --profile <profile> --dry-run`),PowerShell 用 `-DryRun`(如 `pwsh -File scripts/install.ps1 -Profile <profile> -DryRun`)。PowerShell 侧写 `--dry-run` 不生效。
99
+
100
+ > **提示**:环境变量 `PATH` 里没有 `dsh` 命令时,脚本回退用 `npx` 拉起安装器:先打印将要执行的命令并要求确认,确认后才执行;设置环境变量 `DSH_INSTALL_YES=1` 可跳过确认。
101
+
102
+ > **提示**:安装完成后脚本会读取 profile 的 `node_modules/.modules.yaml`,若记录了 `ignoredBuilds`(pnpm 拦截的依赖构建脚本),会打印可复制的豁免指引——把 `ignoredBuilds` 条目所在行原文整行作为 `pnpm-workspace.yaml` 中 `allowBuilds` 的键、值写 `true`;键必须与 lockfile 逐字一致,只写纯包名不生效。
71
103
 
72
- > **提示:**装好后**刷新一下浏览器页面**,右侧栏才会出现「SSH 隧道」入口——只重启宿主不够,客户端产物是页面加载时取的。
104
+ > **提示**:装好后**刷新一下浏览器页面**,右侧栏才会出现「SSH 隧道」入口——只重启宿主不够,客户端产物是页面加载时取的。
73
105
 
74
106
  **装完怎么用**
75
107
 
@@ -104,42 +136,51 @@ irm https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/inst
104
136
  | `read_session` / `send_input` / `resize_session` | 交互式会话的读取、输入与窗口尺寸 |
105
137
 
106
138
  - 会话策略:`reuse_or_create`(默认,会复活同主机已断开的会话)、`new`、`require_existing`,或显式传 `session_id`
107
- - `timeout_ms` 取值 1000–300000,作用于 exec / SFTP / shell;默认 exec 与 SFTP 元数据 30s,SFTP 传输 120s
139
+ - 超时:默认 exec 30s、SFTP 元数据 30s、SFTP 传输 120s、shell 类操作(`read_session` / `send_input` / `resize_session`)10s;`timeout_ms` 可覆盖,取值钳制在 1000–300000,整次调用另有 300s 上限
140
+ - `max_bytes`:exec 与 `sftp_read_text` 的单次输出上限,按字节钳制在 1024–1048576(默认 exec 262144 即 256 KiB、`sftp_read_text` 524288 即 512 KiB);达到上限时结果带 `truncated: true` 且只含前 `max_bytes` 字节
141
+ - exec 结算:结果带 `exitKnown` / `code` / `signal` / `connectionDropped`——正常退出 `exitKnown: true` 且 `code` 为退出码;按信号终止时返回 `signal`;连接被切断时 `code` 为 `null` 且 `connectionDropped: true`
142
+ - 交互式输出的读取走 seq 游标协议(侧栏轮询的 `shellRead`):客户端携带上次读到的 `since`,响应返回 `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`;环形缓冲(512 KiB)淘汰后 `baseSeq` 前移且 `dropped: true`,客户端据此全量重取;单次响应的 `chunk` 上限 256 KiB
108
143
  - 认证方式只有**密码**或**私钥**;键盘交互式认证(含堡垒机网页 MFA)不支持
109
144
 
110
145
  ## 安全
111
146
 
147
+ **信任边界(客观陈述)**:插件的 HTTP API 挂在 DSH 宿主的本地 web 服务上,只接受本机回环(`localhost` / `127.0.0.1` / `::1`)与配置的可信主机,并校验浏览器 `Origin`;API 没有鉴权 token——**能在本机访问该端口的进程都被视为已授权用户**,可以使用本插件的能力。会话 API 必须带 `projectPathKey`,跨项目不可见。
148
+
149
+ 授权与路径守卫动作清单:
150
+
151
+ - **连接必须先授权**:未在「项目授权」里勾选的主机无法建立连接,Connect 不会自动写入授权
152
+ - **授权撤销即时生效**:写类操作在发起前与结果回报前各复检一次授权;撤销后输出被丢弃并以 `not authorized` 失败,下载中的本地文件被删除,受影响的存活会话被关闭,重连拒绝复活已撤销主机
153
+ - **本地路径守卫 fail-closed**:上传 / 下载 / 列举 / 建目录 / 删除 / 重命名的本机路径限制在**项目工作区根**内,词法 **与** realpath 双重校验,越界路径与指向工作区外的符号链接一律拒绝;工作区根取不到时直接拒绝操作(可用环境变量 `DSH_SSH_TUNNEL_WORKSPACE_ROOT` 显式指定)
112
154
  - 工具结果与列表 API 不返回 password / PEM / 口令
113
- - 本地上传、下载、列举、删除的路径限制在**项目工作区根**内(由宿主按当前会话工作区解析,不假设固定挂载点):词法 **与** realpath 双重校验,越界路径与指向工作区外的符号链接一律拒绝
114
155
  - 主机密钥以 **SHA256 hex** 存入 `known_hosts.json`;首次连接或指纹变更时在侧栏确认(展示指纹)
115
- - HTTP API 限制在 loopback / trusted hosts;浏览器 `Origin` 必须匹配;会话 API 必须带 `projectPathKey`
116
156
  - 优先使用密钥登录;若 `secrets.json` 可能泄露请立即轮换凭据
117
- - 键盘交互式认证已下线:请把这类主机改为密码或私钥
157
+ - 认证方式为密码或私钥
118
158
 
119
159
  ## 数据放在哪
120
160
 
121
- `$DSH_HOME/ssh-tunnel/`(目录 `0700`):
161
+ `$DSH_HOME/ssh-tunnel/`(目录权限 `0700`;此为 POSIX 系统行为——Windows 的 NTFS 权限由继承 ACL 决定,`chmod` 不改 DACL):
122
162
 
123
- | 文件 | 内容 |
124
- |---|---|
125
- | `hosts.json` | 主机元数据(不含密钥明文) |
126
- | `secrets.json` | 密码 / PEM / 口令(`0600`) |
127
- | `grants.json` | `projectPathKey → hostIds[]` |
128
- | `known_hosts.json` | 已信任的主机密钥指纹 |
163
+ | 文件 | 内容 | 权限(POSIX) |
164
+ |---|---|---|
165
+ | `hosts.json` | 主机元数据(不含密钥明文) | `0600` |
166
+ | `secrets.json` | 密码 / PEM / 口令 | `0600` |
167
+ | `grants.json` | `projectPathKey → hostIds[]` | `0600` |
168
+ | `known_hosts.json` | 已信任的主机密钥指纹 | `0600` |
129
169
 
130
170
  ## 界面国际化
131
171
 
132
172
  - 命名空间:`sshTunnel`;字典 `zh` / `en` 注册到 `ctx.locale`
133
- - Tab 标题与面板随 DSH 界面语言即时切换
173
+ - Tab 标题与面板随 DSH 界面语言即时切换;切换语言会使插件面板重新挂载——终端与 SFTP 视图随之重建,远端会话本身不受影响
134
174
  - 宿主侧 `SSHManager` 的描述保持英文(面向模型)
135
175
 
136
176
  ## 开发
137
177
 
138
- ```powershell
139
- npm test # node --test:自检脚本全量回归
140
- npm run check # 语法 + 自检
141
- bash scripts/sync-to-dsh.sh # 以 link: 方式把本仓库接入 web profile(开发用)
142
- bash scripts/install.sh --dry-run # 只看安装计划,不动 profile
178
+ ```sh
179
+ npm test # 自检脚本全量回归(实际执行 node scripts/smoke-test.mjs)
180
+ npm run check # 语法检查 + 自检
181
+ node scripts/smoke-test.mjs # 直接运行自检(离线设计,不起 SSH、不起 DSH 进程)
182
+ bash scripts/install.sh --profile <profile> --dry-run # 只看安装计划,不动 profile
183
+ bash scripts/sync-to-dsh.sh --dry-run # 预览 link: 接入命令;去掉 --dry-run 才会改写 profile
143
184
  node scripts/portal-probe.mjs # 客户端 Tab 渲染探针(能解析到 react 时生效,否则明确跳过)
144
185
  ```
145
186
 
@@ -148,10 +189,11 @@ node scripts/portal-probe.mjs # 客户端 Tab 渲染探针(能解析到
148
189
  ```text
149
190
  lib/index.js 宿主入口:工具注册、/dsh-ssh-tunnel/api 路由、授权与路径守卫、xterm 资产下发
150
191
  lib/client.js 客户端 bundle(已入库;dsh plugin add 不做构建)
151
- lib/session.js 会话生命周期:连接、keepalive、掉线墓碑与自动重连
152
- lib/shared/ 宿主与客户端共用纯函数:path / host-key / host-summary / http-trust / persist /
192
+ lib/session.js 会话生命周期:连接、keepalive、掉线墓碑与自动重连、exec/SFTP 执行
193
+ lib/shared/ 宿主与客户端共用纯函数:path / args / host-key / host-summary / http-trust / persist /
153
194
  session-auth / session-policy / shell-buffer / vendor
154
195
  scripts/ install.sh · install.ps1 · sync-to-dsh.sh · smoke-test.mjs · portal-probe.mjs
196
+ scripts/lib/ 安装链共享逻辑(.cjs,install.sh 与 install.ps1 共用)
155
197
  cordis.patch.yml 包内 bundle patch,CLI 据此写入 dsh.profile.bundles
156
198
  ```
157
199
 
@@ -163,11 +205,11 @@ cordis.patch.yml 包内 bundle patch,CLI 据此写入 dsh.profile.bundl
163
205
 
164
206
  - 如果这个插件对你有帮助,欢迎点亮 Star;有问题或建议请提 [Issue](https://github.com/OMSociety/dsh-ssh-tunnel/issues) 或 [Pull Request](https://github.com/OMSociety/dsh-ssh-tunnel/pulls)。
165
207
  - 变更记录见 [CHANGELOG](CHANGELOG.md)。
166
- - [LiveAgent](https://github.com/thirsty5034/LiveAgent):产品形态与部分 UX 的参考来源(见上文「参考来源」)
208
+ - LiveAgent([thirsty5034/LiveAgent](https://github.com/thirsty5034/LiveAgent)):产品形态与部分 UX 的参考来源(见上文「参考来源」)
167
209
  - [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar):右侧栏宿主与 Tab 契约
168
210
  - [dsh-git-forge](https://github.com/OMSociety/dsh-git-forge):同门插件,Git 凭据与 push 策略
169
- - [DeepSeek Harness](https://github.com/deepseek-ai/dsh):插件、工具与 agent shell 的宿主
211
+ - DeepSeek Harness:插件、工具与 agent shell 的宿主
170
212
 
171
213
  ## 许可证与作者
172
214
 
173
- [MIT](LICENSE)。上游项目与代码作者 [@thirsty5034](https://github.com/thirsty5034);本仓库的维护与新增部分 © 2026 [@OMSociety](https://github.com/OMSociety)。
215
+ [MIT](LICENSE)。授权条款与版权归属以 LICENSE 为准;上游项目与代码作者 [@thirsty5034](https://github.com/thirsty5034)。
package/README_en.md CHANGED
@@ -1,24 +1,23 @@
1
1
  <p align="center"><a href="README.md">简体中文</a> | <strong>English</strong></p>
2
2
 
3
3
  <div align="center">
4
- <h1>dsh-ssh-tunnel</h1>
4
+ <img src="https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/docs/logo.png" alt="DSH SSH Tunnel" width="160">
5
+ <h1>DSH SSH Tunnel</h1>
5
6
  <p>A multi-host SSH workbench for DeepSeek Harness: host inventory, per-project grants, and a terminal plus dual-pane SFTP in the sidebar.</p>
6
7
  <p>The model drives the <strong>SSHManager</strong> tool to run commands and move files; you manage hosts and grants in the sidebar. <strong>Secrets never enter model context</strong>.</p>
7
8
 
8
9
  <p>
9
- <a href="https://github.com/OMSociety/dsh-ssh-tunnel/releases"><img src="https://img.shields.io/badge/version-1.0.0-4f6ef7" alt="Version"></a>
10
- <a href="https://github.com/deepseek-ai/dsh"><img src="https://img.shields.io/badge/DSH-%3E%3D0.1.7--rc.2_%3C0.3.0--0-4f6ef7" alt="DSH"></a>
10
+ <a href="https://github.com/OMSociety/dsh-ssh-tunnel/releases"><img src="https://img.shields.io/github/v/tag/OMSociety/dsh-ssh-tunnel?label=version&color=4f6ef7" alt="Version"></a>
11
+ <img src="https://img.shields.io/badge/DSH-%3E%3D0.1.7--rc.2%20%3C0.2.0--0%20%7C%7C%20%3E%3D0.2.0--rc.1%20%3C0.3.0--0-4f6ef7" alt="DSH">
11
12
  <a href="LICENSE"><img src="https://img.shields.io/github/license/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="License"></a>
12
13
  <a href="https://github.com/OMSociety/dsh-ssh-tunnel/stargazers"><img src="https://img.shields.io/github/stars/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Stars"></a>
13
14
  <a href="https://github.com/OMSociety/dsh-ssh-tunnel/issues"><img src="https://img.shields.io/github/issues/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Issues"></a>
14
15
  </p>
15
16
  </div>
16
17
 
17
- > **Note:** This is the maintained line of `dsh-ssh-tunnel`, continued independently at the [OMSociety repository](https://github.com/OMSociety/dsh-ssh-tunnel) (standalone since 2026-09-28). The upstream project and the author of this code is [thirsty5034/dsh-ssh-tunnel](https://github.com/thirsty5034/dsh-ssh-tunnel) (MIT, see [LICENSE](LICENSE)). Fixes and issue reports are handled in this repository.
18
-
19
18
  ## What this is
20
19
 
21
- **dsh-ssh-tunnel** is a community plugin for [DeepSeek Harness](https://github.com/deepseek-ai/dsh), mounted in the sidebar host [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar). It collects your SSH hosts into one **host inventory**, authorizes them per project, and then opens an **interactive terminal** (xterm) or **dual-pane SFTP** in the center panel.
20
+ **DSH SSH Tunnel** is a community plugin for DeepSeek Harness, mounted in the sidebar host [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar). It collects your SSH hosts into one **host inventory**, authorizes them per project, and then opens an **interactive terminal** (xterm) or **dual-pane SFTP** in the center panel.
22
21
 
23
22
  It does **not** replace the global `fs` / `subprocess` with a remote disk: remote operations happen only in the `SSHManager` tool you call explicitly and inside the panel, and local and remote files remain two clearly separated sides.
24
23
 
@@ -45,29 +44,62 @@ This package is a **DSH-native implementation** (Cordis host/client plugin, `dsh
45
44
 
46
45
  ## Quick start
47
46
 
48
- **Option 1: install from the CLI (recommended)**
47
+ **Desktop app (recommended path)**
49
48
 
50
- ```powershell
51
- # 1) stop dsh web first (a running server holds the dependency lock; start it again afterwards)
52
- dsh plugin --profile web add "github:OMSociety/dsh-ssh-tunnel"
53
- # 2) restart dsh web
49
+ The DeepSeek Harness desktop app manages plugins itself: install and upgrade this plugin from the app's **plugin page**. The desktop profile directory is owned exclusively by the app — do not run the CLI commands or install scripts below against it.
50
+
51
+ **CLI install (self-hosted web / headless profiles only)**
52
+
53
+ The three options below apply only to self-hosted web / headless profiles. Replace `<profile>` with your profile name.
54
+
55
+ Option 1: install from npm
56
+
57
+ ```sh
58
+ dsh plugin --profile <profile> add "dsh-ssh-tunnel@1.0.2"
59
+ ```
60
+
61
+ The package is published to npm and ships the prebuilt artifacts, so no local build step is involved. Replace `@1.0.2` to install another version.
62
+
63
+ Option 2: install from the GitHub source
64
+
65
+ ```sh
66
+ dsh plugin --profile <profile> add "github:OMSociety/dsh-ssh-tunnel"
54
67
  ```
55
68
 
56
69
  To reproduce a specific install, pin a ref by appending `#<tag or commit sha>` to the repository URL.
57
70
 
58
- **Option 2: one-line installer**
71
+ Option 3: one-line installer
59
72
 
60
73
  ```sh
61
- curl -fsSL https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.sh | bash
74
+ curl -fsSL https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.sh | bash -s -- --profile <profile>
62
75
  ```
63
76
 
64
77
  ```powershell
65
- irm https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.ps1 | iex
78
+ & ([scriptblock]::Create((irm https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.ps1))) -Profile <profile>
66
79
  ```
67
80
 
68
- Besides installing, the script adds this plugin to the profile's `minimumReleaseAgeExclude`, verifies that `dsh.profile.bundles` really received the entry, and removes the mount older versions wrote by hand into the profile's `cordis.patch.yml`. Add `--dry-run` to print the plan without touching anything.
81
+ Installer parameters (bash and PowerShell map one to one):
82
+
83
+ | Purpose | bash | PowerShell |
84
+ |---|---|---|
85
+ | Version to install (optional; latest release by default) | `[version]` (first positional argument) | `-Version <version>` |
86
+ | **Target profile (required, no default)** | `--profile <name>` | `-Profile <name>` |
87
+ | Install source (default `github`) | `--from github\|npm` | `-From github\|npm` |
88
+ | Profile fix (optional switch) | `--fix-profile` | `-FixProfile` |
89
+ | Restart web (optional switch) | `--restart` | `-Restart` |
90
+ | Dry run (optional switch) | `--dry-run` | `-DryRun` |
91
+
92
+ - A missing or nonexistent profile fails the script with a list of the profiles that do exist, exit code 2
93
+ - `--fix-profile` / `-FixProfile`: only with this switch does the script add the plugin to the profile's `pnpm-workspace.yaml` (`minimumReleaseAgeExclude`, idempotent) and remove the hand-written mount older versions put into the profile's `cordis.patch.yml`; writes are read back and asserted, and a failure rolls the change back and exits non-zero. Without the switch both files stay untouched
94
+ - `--restart` / `-Restart`: restarts a self-hosted web service (pm2 process name `dsh-web`); on the desktop app use its plugin page instead
95
+
96
+ > **Note**: The dry-run flag differs between the two scripts — bash takes `--dry-run` (for example `bash scripts/install.sh --profile <profile> --dry-run`), PowerShell takes `-DryRun` (for example `pwsh -File scripts/install.ps1 -Profile <profile> -DryRun`). `--dry-run` has no effect on the PowerShell side.
97
+
98
+ > **Note**: When no `dsh` command is on `PATH`, the script falls back to `npx` to run the installer: it prints the command it is about to execute and asks for confirmation first; set the environment variable `DSH_INSTALL_YES=1` to skip the prompt.
99
+
100
+ > **Note**: After installing, the script reads the profile's `node_modules/.modules.yaml`; if it records `ignoredBuilds` (dependency build scripts blocked by pnpm), it prints a copy-paste exemption recipe — use the whole `ignoredBuilds` entry line verbatim as the key in `allowBuilds` inside `pnpm-workspace.yaml` with the value `true`. The key must match the lockfile literally; a bare package name does not work.
69
101
 
70
- > **Note:** After installing, **refresh the browser page** for the "SSH Tunnel" entry to appear in the sidebar — restarting the host alone is not enough, because the client artifact is fetched when the page loads.
102
+ > **Note**: After installing, **refresh the browser page** for the "SSH Tunnel" entry to appear in the sidebar — restarting the host alone is not enough, because the client artifact is fetched when the page loads.
71
103
 
72
104
  **First run**
73
105
 
@@ -102,42 +134,51 @@ One sidebar tab with three pages:
102
134
  | `read_session` / `send_input` / `resize_session` | Read, feed and resize an interactive session |
103
135
 
104
136
  - Session strategies: `reuse_or_create` (default; revives a disconnected session for that host), `new`, `require_existing`, or an explicit `session_id`
105
- - `timeout_ms` ranges from 1000 to 300000 and applies to exec / SFTP / shell; defaults are 30s for exec and SFTP metadata, 120s for SFTP transfers
137
+ - Timeouts: exec 30s, SFTP metadata 30s, SFTP transfers 120s, shell operations (`read_session` / `send_input` / `resize_session`) 10s by default; `timeout_ms` overrides them, clamped to 1000–300000, with a 300s ceiling for the whole call
138
+ - `max_bytes`: per-call output cap for exec and `sftp_read_text`, clamped to 1024–1048576 bytes (defaults: 262144 = 256 KiB for exec, 524288 = 512 KiB for `sftp_read_text`); when the cap is hit the result sets `truncated: true` and carries only the first `max_bytes` bytes
139
+ - exec settlement: results carry `exitKnown` / `code` / `signal` / `connectionDropped` — a normal exit sets `exitKnown: true` with the exit code in `code`; a signal death returns `signal`; a cut connection returns `code: null` with `connectionDropped: true`
140
+ - Interactive output is read through a seq cursor protocol (the sidebar polling API `shellRead`): the client sends the last `since` it has seen, and the response returns `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`; ring-buffer eviction (512 KiB) moves `baseSeq` forward and sets `dropped: true`, telling the client to refetch in full; a single response caps `chunk` at 256 KiB
106
141
  - Authentication is **password** or **private key** only; keyboard-interactive (including bastion web MFA) is not supported
107
142
 
108
143
  ## Security
109
144
 
145
+ **Trust boundary (stated as it is)**: the plugin's HTTP API is served by the DSH host's local web server and only accepts loopback (`localhost` / `127.0.0.1` / `::1`) plus configured trusted hosts, with the browser `Origin` checked; the API has no authentication token — **any local process that can reach the port is treated as an authorized user** and may use the plugin's capabilities. Session APIs require `projectPathKey` and are invisible across projects.
146
+
147
+ Authorization and path-guard action list:
148
+
149
+ - **Grant before connect**: a host that is not checked under Project access cannot be connected, and Connect never auto-writes grants
150
+ - **Revocation takes effect immediately**: write operations re-check the grant once before being issued and once before the result is delivered; after a revocation the output is discarded and the call fails with `not authorized`, a local file mid-download is deleted, affected live sessions are closed, and reconnect refuses to resurrect the revoked host
151
+ - **Local path guard fails closed**: local upload / download / list / mkdir / delete / rename paths are constrained to the **project workspace root**, with both lexical and realpath checks, and out-of-bounds paths or symlinks pointing outside the workspace are rejected; when no workspace root can be resolved the operation is refused (the environment variable `DSH_SSH_TUNNEL_WORKSPACE_ROOT` can set one explicitly)
110
152
  - Tool results and list APIs never return password / PEM / passphrase
111
- - Local upload, download, list and delete paths are constrained to the **project workspace root** (the host resolves it from the current session workspace rather than assuming a fixed mount point): both lexical and realpath checks apply, and out-of-bounds paths or symlinks pointing outside the workspace are rejected
112
153
  - Host keys are stored as **SHA256 hex** in `known_hosts.json`; the first connect or a fingerprint change is confirmed in the sidebar with the fingerprint shown
113
- - The HTTP API is fenced to loopback / trusted hosts; the browser `Origin` must match; session APIs require `projectPathKey`
114
154
  - Prefer key-based auth; rotate credentials immediately if `secrets.json` may have leaked
115
- - keyboard-interactive is retired: edit such hosts to password or private key
155
+ - Authentication is password or private key
116
156
 
117
157
  ## Where data lives
118
158
 
119
- Under `$DSH_HOME/ssh-tunnel/` (directory mode `0700`):
159
+ Under `$DSH_HOME/ssh-tunnel/` (directory mode `0700`; this is POSIX behavior — on Windows NTFS permissions come from inherited ACLs and `chmod` does not change the DACL):
120
160
 
121
- | File | Contents |
122
- |---|---|
123
- | `hosts.json` | Host metadata (no secret material) |
124
- | `secrets.json` | Passwords / PEM / passphrases (`0600`) |
125
- | `grants.json` | `projectPathKey → hostIds[]` |
126
- | `known_hosts.json` | Trusted host key fingerprints |
161
+ | File | Contents | Mode (POSIX) |
162
+ |---|---|---|
163
+ | `hosts.json` | Host metadata (no secret material) | `0600` |
164
+ | `secrets.json` | Passwords / PEM / passphrases | `0600` |
165
+ | `grants.json` | `projectPathKey → hostIds[]` | `0600` |
166
+ | `known_hosts.json` | Trusted host key fingerprints | `0600` |
127
167
 
128
168
  ## UI internationalization
129
169
 
130
170
  - Namespace: `sshTunnel`; dictionaries `zh` / `en` registered on `ctx.locale`
131
- - Tab title and panel follow the DSH interface language live
171
+ - Tab title and panel follow the DSH interface language live; switching the language remounts the plugin panel — the terminal and SFTP views are rebuilt, while the remote session itself is unaffected
132
172
  - Host-side `SSHManager` strings stay English (model-facing)
133
173
 
134
174
  ## Development
135
175
 
136
- ```powershell
137
- npm test # node --test: full regression of the smoke scripts
138
- npm run check # syntax + smoke tests
139
- bash scripts/sync-to-dsh.sh # register this checkout into the web profile as link: (dev)
140
- bash scripts/install.sh --dry-run # print the install plan without touching the profile
176
+ ```sh
177
+ npm test # full regression of the smoke scripts (runs node scripts/smoke-test.mjs)
178
+ npm run check # syntax check + smoke tests
179
+ node scripts/smoke-test.mjs # run the smoke scripts directly (offline by design: no SSH, no DSH process)
180
+ bash scripts/install.sh --profile <profile> --dry-run # print the install plan without touching the profile
181
+ bash scripts/sync-to-dsh.sh --dry-run # preview the link: registration; drop --dry-run to actually rewrite the profile
141
182
  node scripts/portal-probe.mjs # client tab render probe (runs when react resolves, otherwise skips)
142
183
  ```
143
184
 
@@ -146,10 +187,11 @@ Layout and where to change what:
146
187
  ```text
147
188
  lib/index.js Host entry: tool registration, /dsh-ssh-tunnel/api routes, grants and path guard, xterm asset serving
148
189
  lib/client.js Client bundle (committed; dsh plugin add does not build)
149
- lib/session.js Session lifecycle: connect, keepalive, drop tombstones and auto-reconnect
150
- lib/shared/ Pure functions shared by host and client: path / host-key / host-summary / http-trust / persist /
190
+ lib/session.js Session lifecycle: connect, keepalive, drop tombstones and auto-reconnect, exec/SFTP execution
191
+ lib/shared/ Pure functions shared by host and client: path / args / host-key / host-summary / http-trust / persist /
151
192
  session-auth / session-policy / shell-buffer / vendor
152
193
  scripts/ install.sh · install.ps1 · sync-to-dsh.sh · smoke-test.mjs · portal-probe.mjs
194
+ scripts/lib/ Installer shared logic (.cjs, used by both install.sh and install.ps1)
153
195
  cordis.patch.yml In-package bundle patch the CLI turns into dsh.profile.bundles
154
196
  ```
155
197
 
@@ -161,11 +203,11 @@ cordis.patch.yml In-package bundle patch the CLI turns into dsh.profile.b
161
203
 
162
204
  - If this plugin helps you, a Star is welcome; questions and suggestions go to [Issues](https://github.com/OMSociety/dsh-ssh-tunnel/issues) or [Pull Requests](https://github.com/OMSociety/dsh-ssh-tunnel/pulls).
163
205
  - Changes are recorded in the [CHANGELOG](CHANGELOG.md).
164
- - [LiveAgent](https://github.com/thirsty5034/LiveAgent): prior art for the product shape and several UX patterns (see "Prior art" above)
206
+ - LiveAgent ([thirsty5034/LiveAgent](https://github.com/thirsty5034/LiveAgent)): prior art for the product shape and several UX patterns (see "Prior art" above)
165
207
  - [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar): the sidebar host and tab contract
166
208
  - [dsh-git-forge](https://github.com/OMSociety/dsh-git-forge): sibling plugin for Git credentials and push policy
167
- - [DeepSeek Harness](https://github.com/deepseek-ai/dsh): the host for plugins, tools and agent shells
209
+ - DeepSeek Harness: the host for plugins, tools and agent shells
168
210
 
169
211
  ## License and author
170
212
 
171
- [MIT](LICENSE). Upstream project and code author [@thirsty5034](https://github.com/thirsty5034); maintenance and additions in this repository © 2026 [@OMSociety](https://github.com/OMSociety).
213
+ [MIT](LICENSE). The license terms and copyright are defined by LICENSE; upstream project and code author [@thirsty5034](https://github.com/thirsty5034).
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
- # dsh-ssh-tunnel bundle mount
2
- - insert:
3
- - id: ssh-tunnel
4
- name: dsh-ssh-tunnel
1
+ # dsh-ssh-tunnel bundle mount
2
+ - insert:
3
+ - id: ssh-tunnel
4
+ name: dsh-ssh-tunnel
package/icon.svg ADDED
@@ -0,0 +1,26 @@
1
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="ssh_v2_3_host" x1="18" y1="6" x2="18" y2="30" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#4E6EF2"/>
5
+ <stop offset="1" stop-color="#1434B8"/>
6
+ </linearGradient>
7
+ <linearGradient id="ssh_v2_3_flow" x1="14" y1="18" x2="22" y2="18" gradientUnits="userSpaceOnUse">
8
+ <stop stop-color="#5CE1E6"/>
9
+ <stop offset="1" stop-color="#38E298"/>
10
+ </linearGradient>
11
+ </defs>
12
+ <!-- 左服务器节点 -->
13
+ <rect x="7" y="10" width="7" height="16" rx="2" fill="url(#ssh_v2_3_host)"/>
14
+ <circle cx="10.5" cy="14" r="1" fill="#5CE1E6"/>
15
+ <circle cx="10.5" cy="18" r="1" fill="#FFFFFF" fill-opacity="0.8"/>
16
+ <!-- 右服务器节点 -->
17
+ <rect x="22" y="10" width="7" height="16" rx="2" fill="url(#ssh_v2_3_host)"/>
18
+ <circle cx="25.5" cy="14" r="1" fill="#5CE1E6"/>
19
+ <circle cx="25.5" cy="18" r="1" fill="#FFFFFF" fill-opacity="0.8"/>
20
+ <!-- 中间立体光纤加密管道连线 -->
21
+ <path d="M14 15.5H22" stroke="url(#ssh_v2_3_flow)" stroke-width="2.2" stroke-linecap="round"/>
22
+ <path d="M14 20.5H22" stroke="url(#ssh_v2_3_flow)" stroke-width="2.2" stroke-linecap="round"/>
23
+ <!-- 脉冲节点 -->
24
+ <circle cx="18" cy="15.5" r="1.3" fill="#FFFFFF"/>
25
+ <circle cx="18" cy="20.5" r="1.3" fill="#FFFFFF"/>
26
+ </svg>