🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

SSH 远程工作区 chai1110/dsh-ssh-remote

DeepSeek 客户端v0.1.5
chai111039 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

同时连接多台服务器,直接查看编辑远程文件

30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/9/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/chai1110/dsh-ssh-remote.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么[目录]—
生态插件
可安装,未声明 dsh 能力
暂无时间戳·判据 →
装得上吗[实装验证]—
曾在 dsh 0.2.1-rc.5 下真实安装成功,但当前 dsh 已更新至 0.2.2-rc.1,本站尚未在新版下复验
L4 · 真实安装等级说明 →
数据截至 2026/11/01·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/06·判据 →
还在维护吗[仓库]—
更新放缓:最近一次提交在 39 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查⚠ 装前注意

npm 同名包「dsh-ssh-remote」归属 kkutysllb/dsh-kylin-ssh-tunnel,装到的可能不是本插件

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-ssh-remote @ 0.1.5
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/13 08:10:57

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 65 处,展示前 51 条)
  • 写入本地文件
    lib/index.js:182
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    lib/index.js:183
    writeFileSync(
  • 写入本地文件
    lib/index.js:205
    mkdirSync(lp, { recursive: true })
  • 写入本地文件
    lib/index.js:214
    writeFileSync(lp, buf)
  • 读取本地文件
    lib/index.js:225
    const entries = readdirSync(localDir, { withFileTypes: true }).filter((e) => e.name !== '.dsh-remote-meta.json')
  • 读取本地文件
    lib/index.js:243
    const buf = readFileSync(lp)
  • 读取本地文件
    lib/index.js:260
    const j = JSON.parse(readFileSync(machinesFile(), 'utf8'))
  • 写入本地文件
    lib/index.js:266
    try { mkdirSync(path.dirname(machinesFile()), { recursive: true }) } catch {}
  • 写入本地文件
    lib/index.js:267
    writeFileSync(machinesFile(), JSON.stringify({ list, currentId }, null, 2))
  • 读取本地文件
    lib/index.js:354
    key = readFileSync(keyPath)
  • 读取本地文件
    lib/index.js:844
    if (!existsSync(local)) throw new Error(`rw_push: local mirror does not exist — run rw_sync first (${local})`)
  • 读取本地文件
    lib/index.js:1172
    if (!existsSync(lp)) throw new Error(`rw_upload: local file not found: ${lp}`)
  • 发起网络请求
    lib/client.js:29
    const res = await fetch(path, opts)
  • 发起网络请求
    lib/client.js:476
    fetch('/api/dsh-easyssh/state')
  • 读取本地文件
    lib/forwards.js:34
    const j = JSON.parse(readFileSync(this.file, 'utf8'))
  • 写入本地文件
    lib/forwards.js:43
    mkdirSync(path.dirname(this.file), { recursive: true })
  • 写入本地文件
    lib/forwards.js:44
    writeFileSync(this.file, JSON.stringify({ defs: this.defs }, null, 2))
  • 执行外部命令来自配置/变量
    lib/credential.js:22
    execFile(bin, args, { timeout: TIMEOUT, maxBuffer: 1 << 20, ...(input != null ? { input } : {}) }, (err, stdout, stderr) => {
  • 写入本地文件
    lib/credential.js:57
    mkdirSync(secretsDir || '.', { recursive: true })
  • 写入本地文件
    lib/credential.js:58
    writeFileSync(joinPath(secretsDir || '.', acc + '.bin'), b64, 'utf8')
  • 读取本地文件
    lib/credential.js:77
    const b64 = readFileSync(joinPath(secretsDir || '.', acc + '.bin'), 'utf8').trim()
  • 删除本地文件
    lib/credential.js:100
    try { unlinkSync(joinPath(secretsDir || '.', acc + '.bin')) } catch { }
  • 读取本地文件
    lib/ignore.js:117
    const text = readFileSync(ignoreFile, 'utf8')
  • 读取本地文件
    lib/sshconfig.js:14
    return readFileSync(filePath, 'utf8')
  • 写入本地文件
    lib/sync.js:70
    if (!existsSync(lp)) mkdirSync(lp, { recursive: true })
  • 读取本地文件
    lib/sync.js:70
    if (!existsSync(lp)) mkdirSync(lp, { recursive: true })
  • 读取本地文件
    lib/sync.js:94
    if (existsSync(lp)) {
  • 读取本地文件
    lib/sync.js:95
    try { lk = localKey(statSync(lp)) } catch { lk = null }
  • 写入本地文件
    lib/sync.js:115
    writeFileSync(lp, buf)
  • 写入本地文件
    lib/sync.js:116
    try { utimesSync(lp, new Date(r.mtime * 1000), new Date(r.mtime * 1000)) } catch {}
  • 读取本地文件
    lib/sync.js:146
    const entries = readdirSync(lDir, { withFileTypes: true }).filter(
  • 读取本地文件
    lib/sync.js:170
    try { l = statSync(lp) } catch { return }
  • 读取本地文件
    lib/sync.js:195
    const buf = readFileSync(lp)
  • 写入本地文件
    lib/sync.js:202
    try { utimesSync(lp, new Date(rmt * 1000), new Date(rmt * 1000)) } catch {}
  • 写入本地文件
    lib/sync.js:229
    mkdirSync(localDir, { recursive: true })
  • 写入本地文件
    lib/sync.js:231
    writeFileSync(tmp, JSON.stringify(state, null, 1))
  • 读取本地文件
    lib/update.js:36
    const pkg = JSON.parse(readFileSync(path.join(selfDir(), 'package.json'), 'utf8'))
  • 发起网络请求
    lib/update.js:61
    const res = await fetch(REGISTRY_URL, { signal: controller.signal, headers: { accept: 'application/json' } })
  • 发起网络请求
    lib/update.js:109
    res = await fetch(tarballUrl, { signal: controller.signal })
  • 写入本地文件
    lib/update.js:119
    mkdirSync(tmpPkg, { recursive: true })
  • 写入本地文件
    lib/update.js:126
    mkdirSync(path.dirname(dest), { recursive: true })
  • 写入本地文件
    lib/update.js:127
    writeFileSync(dest, f.data)
  • 读取本地文件
    lib/update.js:132
    const newPkg = JSON.parse(readFileSync(path.join(tmpPkg, 'package.json'), 'utf8'))
  • 读取本地文件
    lib/update.js:137
    if (!existsSync(newIndex) || statSize(newIndex) < 100) throw new Error('tarball missing lib/index.js')
  • 写入本地文件
    lib/update.js:144
    if (existsSync(src)) copyFileSync(src, path.join(installLib, name))
  • 读取本地文件
    lib/update.js:144
    if (existsSync(src)) copyFileSync(src, path.join(installLib, name))
  • 写入本地文件
    lib/update.js:148
    if (existsSync(src)) copyFileSync(src, path.join(dir, rel))
  • 读取本地文件
    lib/update.js:148
    if (existsSync(src)) copyFileSync(src, path.join(dir, rel))
  • 写入本地文件
    lib/update.js:150
    writeFileSync(path.join(dir, '.dsh-remote-updated'), String(targetVersion))
  • 删除本地文件递归
    lib/update.js:155
    try { rmSync(tmpRoot, { recursive: true, force: true }) } catch {}
  • 读取本地文件
    lib/update.js:162
    return statSync(p).size
扫描于 2026/11/6(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 同时连接多台服务器
  • 查看编辑远程文件
  • 执行远程文件

README

由 DeepSeek 最新模型翻译生成
dsh-ssh-remote 📖 English DeepSeek Harness (DSH) 的 SSH 远程工作区插件——多机并行版:管理多台服务器、同时保持多个 SSH 连接,在每台上选择远程工作区,Agent 可直接查看 / 编辑 / 执行远程文件。 ⛔ 维护状态:已暂停(2026-09-10) 本仓库后续不再跟进 DSH 官方新版本的适配。 - 最后一次适配基准:@deepseek-ai/dsh@0.1.2-rc.1。 - 未验证版本:0.1.5-rc.1 及以后。仓库不会再针对这些版本做适配、修复或发版。 - 代码与安装脚本保持现状可用(仍是 MIT,仍可自行 fork 继续维护),但在 0.1.5+ 上不保证工作。
展开完整文档(README 剩余 1 节)
dsh-ssh-remote
📖 English

DeepSeek Harness (DSH) 的 SSH 远程工作区插件——多机并行版:管理多台服务器、同时保持多个 SSH 连接,在每台上选择远程工作区,Agent 可直接查看 / 编辑 / 执行远程文件。

⛔ 维护状态:已暂停(2026-09-10)

本仓库后续不再跟进 DSH 官方新版本的适配。

- 最后一次适配基准:@deepseek-ai/dsh@0.1.2-rc.1。
- 未验证版本:0.1.5-rc.1 及以后。仓库不会再针对这些版本做适配、修复或发版。
- 代码与安装脚本保持现状可用(仍是 MIT,仍可自行 fork 继续维护),但在 0.1.5+ 上不保证工作。

为什么停下来:DSH 0.1.5-rc.1 引入了一批与本插件直接相关的破坏性变更,跟进成本不再划算——

| 官方变更(0.1.5-rc.1) | 对本插件的影响 |
| Session 格式升级至 V3(单向迁移) | 会话日志读取路径需重测 |
| 新增 session 锁(同一 session 至多一个进程持有) | 远程/本地双实例并存模型受影响 |
| 移除 ctx.agent,调用方需显式传 Agent | host 侧注入需改 |
| Inbox 改为 type-only(hasPending/claim 不再公开) | 需改 |
| Web 面板 API 变更:新增 sidebar.panellist / main,conversation slot 迁至 main.conversation,Detail 面板移除 | 前端挂载点需整体重做 |
| persona 拆分为前缀 + 后缀 | systemPrompt.section 用法需重测 |

如果你需要 SSH 远程工作区能力:可以 fork 本仓库,或改用上游 flymysql/dsh-remote,或参考 chenw2759-wq/dsh-IDE(本仓库 vendor/ 下 3 个包的来源)。

适配版本:@deepseek-ai/dsh@0.1.2-rc.1(历史基准,见上方维护状态)(profile 上运行的 DSH Web)。
⚠️ 本插件以「同名用户 preset 覆盖」机制挂载,DSH 官方升级后重跑一次安装脚本即可继续使用(已有用户实测在 rc.2 → rc.1 架构重构版下正常工作);老版本 DSH 用户同样适用,见「🔄 适配其他 DSH 版本」。

📌 上游项目与版权声明

本项目是 dsh-remote 的 fork + 改造

本项目基于以下开源项目开发(非完全原创),遵守其 MIT License:

- 上游原版:flymysql/dsh-remote
- npm 包:dsh-remote
- 上游作者:flymysql()
- 上游版本:0.5.10 → 本仓库 0.6.0
- 许可:MIT License,版权见 LICENSE(Copyright (c) 2026 dsh-remote contributors)

本仓库的改造内容

| 维度 | 上游 dsh-remote | 本仓库 dsh-ssh-remote |
| 连接模型 | 单连接池(切换机器时断开旧连接) | 多池并行(每台机器独立 SshPool,可同时连接) |
| 目标选择 | 仅 current 机 | 所有工具/路由支持 machineId 参数(不传 = current 机) |
| 状态查看 | 单机状态 | rw_info / /dsh-ssh-remote/status 列出全部机器及各自连接状态 |
| 新增工具 | — | rw_switch(切换 current)、rw_disconnect(断开指定机) |
| rc.2 适配 | ❌ peerDeps 仍为 ^0.1.0-rc.6,装上会破坏 scope 链 | ✅ 按 rc.2 用户 preset 挂载,进入 agent scope,不破坏核心功能 |

贡献归属

- 底层 SSH 引擎、SFTP 同步、机器注册表、多数 rw_ 工具与前端设置面板:来自 flymysql/dsh-remote
- 多池并行改造、machineId 参数、rw_switch、rc.2 preset 适配:本仓库(chai1110)的增量修改
- 上游如有新版本,欢迎优先参考上游变更并合并:

vendored 组件(vendor/ 目录)

本仓库同时收录了 chenw2759-wq/dsh-IDE(BSD-3-Clause)的两个已构建包,用于在同一个安装里提供 IDE 风格右侧面板与 SSH 引擎:

| 目录 | 包名 | 来源 | 协议 |
| vendor/dsh-aionui-panel | @deepseek-ai/dsh-client-ui-aionui-panel | chenw2759-wq/dsh-IDE | BSD-3-Clause |
| vendor/dsh-ssh | @deepseek-ai/dsh-ssh | chenw2759-wq/dsh-IDE | BSD-3-Clause |

说明:
- dsh-aionui-panel 提供右侧面板(文件树 / 预览 / 编辑 / 终端 / diff)。其面板设计参考 iOfficeAI/AionUi(Apache-2.0)重新实现,详见其 LICENSE。
- dsh-ssh 提供 SSH 引擎(连接池、隧道、网页终端)与 ssh_ agent 工具。
- 每个 vendored 目录都保留了 dsh-IDE 原始的 BSD-3-Clause LICENSE。完整声明见 THIRD_PARTY_NOTICES.md。

与主插件一起安装:

dsh plugin --profile web add file:$(pwd)/vendor/dsh-aionui-panel
dsh plugin --profile web add file:$(pwd)/vendor/dsh-ssh

✨ 功能

- 多机 SSH 注册表:任意多台服务器(host/port/user + 密码或私钥 + 口令),存于 ~/.dsh/remote-workspaces/machines.json
- 同时连接多台:每台机器独立连接池,互不干扰
- 远程工作区:为每台机器选择远程目录(自动补全/浏览),本地镜像 ~/.dsh/remote-workspaces//...
- 双向 SFTP 同步:rw_sync(远程→镜像)、rw_push(镜像→远程)
- Agent 远程操作工具(rw_ 系列):
- rw_info — 查看全部已注册机器及连接状态、当前工作区
- rw_connect / rw_switch — 注册 / 连接 / 切换机器
- rw_pick_workspace — 为指定机器设置远程工作区目录
- rw_list_dir / rw_read_file / rw_write_file / rw_exec — 浏览、读、写、执行远程文件/命令
- rw_sync / rw_push — 远程↔本地镜像 双向 SFTP 同步
- rw_disconnect — 断开指定机器(其他机器不受影响)
- 设置面板:Settings → 远程工作区(多机增删改查、当前机、测试连接)

🚀 安装(一键脚本,推荐)

适配 DSH 0.1.2-rc.1(老版本 0.1.1-rc.2 等同样适用,见「🔄 适配其他 DSH 版本」)。在一台装好 DSH 的机器上,只需三步:

1) 克隆本项目
git clone https://github.com/chai1110/dsh-ssh-remote.git
cd dsh-ssh-remote

2) 一键安装(自动装依赖 + 注册 symlink + 创建 SSH 增强预设) bash install.sh

3) 重启 DSH kill $(pgrep -f 'dsh web') 2>/dev/null; dsh web

然后重启 DSH、打开 DSH Web → 新建会话 → 选你平时用的模式即可(standard / code / minimal / cordis 都已内置 rw_ 工具,无需切换新模式)。

install.sh 自动做了什么:
1. 定位 DSH 全局安装目录(npm root -g)
2. 在本仓库目录安装依赖(ssh2 / schemastery 等),使插件自包含
3. 将插件 symlink 到 ~/.dsh/profiles/web/node_modules/dsh-ssh-remote(让 preset 的 bare name 可解析)
4. 增强每个官方模式:对 standard / code / minimal / cordis 各建同名用户 preset(首次复制官方原版 agent.cordis.yml + preset.yml,再追加 dsh-ssh-remote 插件行;first-root-wins:用户 preset 覆盖官方,模式名不变)——SSH 工具直接出现在你现有的每个模式里
5. 移除旧的「SSH 增强模式」(首个版本遗留,现已不需要单独模式)

脚本幂等:重复运行不会重复添加;卸载用 bash install.sh --uninstall(会从各模式移除 SSH 行并删除 symlink)。

🛠 手动安装(想自己控制每一步时)

与 install.sh 等价,分步如下:

第 1 步:克隆并安装插件依赖
git clone https://github.com/chai1110/dsh-ssh-remote.git
cd dsh-ssh-remote
npm install --no-save # 安装 ssh2 / schemastery 等依赖

第 2 步:注册进 profile 的 node_modules 让 preset 的 bare name dsh-ssh-remote 能被解析(baseUrl = profile 目录):
mkdir -p ~/.dsh/profiles/web/node_modules
ln -sfn "$PWD" ~/.dsh/profiles/web/node_modules/dsh-ssh-remote

第 3 步:增强每个官方模式(SSH 工具加入现有模式)对你要用的每个模式(standard / code / minimal / cordis)逐个执行:把官方 preset 复制到用户 preset 目录 ~/.dsh/.agent-presets//,再追加插件行。以全部四个为例:
GLOBAL_ROOT=$(npm root -g)
for p in standard code minimal cordis; do
mkdir -p ~/.dsh/.agent-presets/$p
首次:复制官方原版(之后保留,不覆盖)
[ -f ~/.dsh/.agent-presets/$p/agent.cordis.yml ] || \
cp "$GLOBAL_ROOT/@deepseek-ai/dsh/config/agent-presets/$p/agent.cordis.yml" \
~/.dsh/.agent-presets/$p/agent.cordis.yml
[ -f ~/.dsh/.agent-presets/$p/preset.yml ] || \
cp "$GLOBAL_ROOT/@deepseek-ai/dsh/config/agent-presets/$p/preset.yml" \
~/.dsh/.agent-presets/$p/preset.yml
追加 SSH 插件行(幂等)
grep -q "name: 'dsh-ssh-remote'" ~/.dsh/.agent-presets/$p/agent.cordis.yml || \
cat >> ~/.dsh/.agent-presets/$p/agent.cordis.yml 原理与 install.sh 第 4 步一致:用户 preset 与官方同名(first-root-wins 覆盖),模式名不变、工具自动多一组。

第 4 步:重启bash kill $(pgrep -f 'dsh web') 2>/dev/null; dsh web 打开 DSH Web → 新会话直接选你平时用的模式(standard / code / minimal / cordis 任选,均已含 rw_* 工具)。

🛠 使用示例

对话方式(模型直接驱动 SSH)

我在 192.168.1.10 和 192.168.1.20 有两台服务器,帮我:
1. rw_connect 添加 192.168.1.10(root,密钥 /Users/me/.ssh/id_rsa)
2. rw_connect 添加 192.168.1.20(root,密码 xxx)
3. rw_pick_workspace (machineId=, path=/srv/app)
4. rw_exec (machineId=, command='docker compose ps')
5. rw_list_dir (machineId=, path=/srv/app) 看文件
6. rw_read_file (machineId=, path=/srv/app/src/main.py)
7. rw_write_file (machineId=, path=/srv/app/config.yml, content='...')
8. rw_sync (machineId=) 把远程工作区镜像到本地 ~/.dsh/remote-workspaces/

工具一览(都支持 machineId 指定目标机,不传 = 当前机)

| 工具 | 作用 |
| rw_info | 查看全部已注册机器及连接状态、当前工作区 |
| rw_connect | 注册/连接一台机器(新机器传 host/user/password/privateKeyPath) |
| rw_switch | 切换当前机器(后续不传 machineId 时默认用它) |
| rw_pick_workspace | 为指定机器设置远程工作区目录 |
| rw_list_dir / rw_read_file | 浏览 / 读取远程文件 |
| rw_write_file | 直接写远程文件(自动建父目录) |
| rw_exec | 在远程执行 shell 命令 |
| rw_sync / rw_push | 远程↔本地镜像 双向 SFTP 同步 |
| rw_disconnect | 断开指定机器(其他机器不受影响) |

设置面板方式

浏览器 DSH Web → 设置 → 远程工作区:增删改查机器、测试连接、切换当前机。

🔄 适配其他 DSH 版本 / 其他设备

本插件适配最新 @deepseek-ai/dsh@0.1.2-rc.1,同时兼容更早的 0.1.1-rc.2 等版本(以 ~/.dsh/.agent-presets/ 下同名用户 preset 覆盖机制挂载进现有模式)。换到其他版本时:

1. DSH 官方升级后:通常 preset 机制不变,bash install.sh --uninstall && bash install.sh 重装即可(脚本幂等,会检测版本)。
2. 其他设备部署:任意机器上 git clone → bash install.sh → 重启 DSH 即可,无需手动复制任何文件(依赖、symlink、preset 全部自动完成)。
3. 多台机器:插件的机器注册表存在 ~/.dsh/remote-workspaces/machines.json,每台设备独立维护;如需多设备共享同一批机器,可手动把该文件复制到新设备。

版本管理方式:本仓库用 git tag 标记适配点(如 v0.1.2-rc.1)。由于 preset 挂载机制跨 DSH 版本通用,同一套代码即可适配各版本——升级 DSH 后重跑 install.sh 即可,无需 checkout 不同 tag。

↩️ 卸载 bash 一键卸载(推荐) bash install.sh --uninstall

或手动:删除 symlink,再从各模式 preset 移除 SSH 插件行
rm -f ~/.dsh/profiles/web/node_modules/dsh-ssh-remote
然后用编辑器在各 ~/.dsh/.agent-presets//agent.cordis.yml 中删除末尾的「SSH 远程工作区」插件块;模式本身保留(其余行是官方原版)

🔗 相关

- 补丁集仓库(输入历史 + 编辑重发):
- 上游 dsh-remote:
- DeepSeek Harness 官方:
预览分享卡片长什么样 ↓chai1110/dsh-ssh-remote
分享 · DeepSeek Harness Hub
chai1110/dsh-ssh-remote

同时连接多台服务器,直接查看编辑远程文件

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部