dsh-remote 0.8.21 → 0.8.23
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/README.md +28 -1
- package/README.zh.md +26 -1
- package/lib/binding.js +9 -3
- package/lib/client.js +275 -56
- package/lib/file-reference.js +457 -0
- package/lib/index.js +456 -84
- package/lib/pool.js +49 -18
- package/lib/routes-fs.js +14 -2
- package/lib/sshconfig.js +415 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
# dsh-remote
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-remote)
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-remote)
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-remote)
|
|
8
10
|
[](LICENSE)
|
|
9
11
|
[](https://github.com/topics/dsh-plugin)
|
|
10
12
|
|
|
13
|
+
Maintained by [@flymysql](https://github.com/flymysql) · [Blog](https://gitpull.cn) · [Discussions](https://github.com/flymysql/dsh-remote/discussions) · [Issues](https://github.com/flymysql/dsh-remote/issues) · [中文说明](./README.zh.md)
|
|
14
|
+
|
|
11
15
|

|
|
12
16
|
|
|
13
17
|
**Remote-work assistant for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH).**
|
|
@@ -35,12 +39,13 @@ Real capture (host scrubbed to a placeholder):
|
|
|
35
39
|
## Features
|
|
36
40
|
|
|
37
41
|
- **Multi-machine SSH** — save any number of hosts (`host`/`port`/`user` + **private key** or **password**). Passwords are stored locally and never shown back in the UI. Switch with one click in Settings. Per-machine **passphrase / host-key mode / SSH agent / keyboard-interactive (OTP) / proxy jump (bastion)** and an **optional OS-keychain password** (`加密保存密码` — macOS Keychain / Windows DPAPI / Linux secret-tool).
|
|
38
|
-
- **`~/.ssh/config`
|
|
42
|
+
- **`~/.ssh/config` aliases (resolved live, never copied)** — a machine can be saved as just a **Host alias** (`useSshConfig`): hostname/user/port/key/jump host are read from `~/.ssh/config` **at every connect**, so editing that file takes effect immediately and there is nothing to re-import; the registry stores **no copy** of those values (the key stays a path reference, its content is never read). Full OpenSSH semantics: multi-alias `Host a b`, `*`/`?` wildcards, `!` negation, `Include` (globbed, relative to `~/.ssh`), trailing-`\` continuations and ssh_config(5)'s *first-obtained-value-wins*. In Settings, **Import from ~/.ssh/config** saves an alias in one click (or **Copy fields** materialises a normal machine), the alias list and machine rows show **alias → what it actually resolves to**, and anything the plugin cannot honour (`ProxyJump` with several hops, `ProxyCommand`) is surfaced as a warning instead of silently degrading.
|
|
39
43
|
- **Two-tab workspace picker** (fills the native "Add workspace" flow):
|
|
40
44
|
- **本机 / Local** — opens the **native OS folder chooser** over the host (macOS `osascript` / Linux `zenity`→`kdialog` / **Windows `FolderBrowserDialog`**), or lets you type a local path → adopted directly as a normal DSH local workspace.
|
|
41
45
|
- **远程 / Remote** — the picker is a **centered modal**. Pick a **machine** → on Windows hosts the root shows a **"This PC" drive view** (`C:\`, `D:\`, `E:\`… instead of the Git Bash MSYS root) and the path field live **autocompletes** directories (accepts `C:\Users\…` or `/c/Users/…` — Windows paths are rewritten to the Git Bash form underneath); selecting a directory immediately lists its next level. A **浏览…** floating browser (Windows-aware breadcrumb `此电脑 / C:\ / Users / dev`, drive rows, size + mtime, dirs first, follows symlinks) fills the field without committing; the **回上一级** button works at any depth (even when the browser was opened at the path bar's value). **最近 workspaces** quick-pick, **`~` 主目录** shortcut and **新建目录** are one click away. On confirm it creates a **real local mirror** under `$DSH_HOME/remote-workspaces/<host>-<user>-<port>/<base>` that passes `fs.realpath` → the harness adopts it as a real workspace while dsh-remote keeps it synced over SFTP.
|
|
42
46
|
- **Git Bash default terminal (Windows remotes)** — the remote platform is auto-detected (`cmd /c ver`, plus an `uname -s` MINGW/MSYS probe as fallback); on Windows the plugin locates Git Bash (`config.shell` can pin a path or `native` disables wrapping) and pipes every command to `bash -s` over the exec channel, so quoting/backslash escaping is never an issue regardless of the SSH default shell. `rw_exec` runs with a Git Bash cwd (`/c/Users/…` form). `/dsh-remote/status`, `rw_info` and the 测试连接 button report the detected platform + shell.
|
|
43
47
|
- **Windows path auto-conversion** — typing `C:\Users\dev\project` (or `C:/…`, `/c/…`, `/C:/…`) is normalized underneath to the Git Bash form `/c/Users/dev/project` for shell commands, while workspaces are stored and shown Windows-style (`C:\Users\dev\project`). All model tools accept and report both forms; SFTP access uses the Win32-OpenSSH `/D:/…` form (see `toSftpPath`).
|
|
48
|
+
- **Remote `@` completion (issue #39)** — in a remote session `@` lists the **remote** tree (read live over SFTP, not the local mirror): directories drill down, a slash-free query fuzzy-matches the whole tree, and candidates are **workspace-relative paths** (`@src/main.c`) exactly like a local session. The `rw_*` tools accept those relative paths and resolve them against the remote workspace root. The index is bounded (entries/directories/deadline + cache + failure breaker) and **falls back to the local mirror when the host is unreachable** — never a silent empty list. Local sessions are untouched.
|
|
44
49
|
- **Bidirectional SFTP sync, conflict-aware** — `rw_sync` (remote → mirror) and `rw_push` (mirror → remote) are **three-way** (remote vs local vs last-synced snapshot): files changed on both sides are **reported as conflicts and never silently overwritten** (`force=true` overrides). Defaults are **depth 8 / 2000 files**; hitting a cap is reported as **`TRUNCATED`**. Both support **dry-run**, **background tasks**, and honor **gitignore-style ignore rules**.
|
|
45
50
|
- **Model tools** — 20 tools, all Windows/POSIX portable via SFTP: `rw_info`, `rw_connect` (with `save`), `rw_pick_workspace`, `rw_list_dir` (size+mtime), `rw_stat`, `rw_read_file` (encoding-aware: utf-8/gbk), `rw_write_file`, **`rw_edit`** (literal replace + mtime optimistic lock), `rw_append`, `rw_mkdir`, `rw_remove` (recursive, bounded), `rw_move`, `rw_exec` (pty/env), **`rw_search`** (SFTP tree walk — works on Windows too, honors ignore rules, context lines), `rw_download`/`rw_upload` (streaming fastGet/fastPut + size caps), **`rw_forward`** (SSH tunnels), `rw_sync`, `rw_push`, `rw_disconnect`.
|
|
46
51
|
- **Port forwarding panel** — create/start/stop/remove **local** (`127.0.0.1:port → remote`) and **reverse** (`remote → local`) tunnels in the Settings page or via `rw_forward`; definitions persist, auto-restart on reconnect when enabled, all tunnels stop on disconnect.
|
|
@@ -237,9 +242,16 @@ and should be done only when you intend to release.
|
|
|
237
242
|
| `autoPush` | bool | `false` | auto-push edited mirror files back to the remote (watcher, debounced) |
|
|
238
243
|
| `auditLog` | bool | `true` | append executed commands to `$DSH_HOME/remote-workspaces/audit.log` |
|
|
239
244
|
| `encoding` | string | `utf-8` | text encoding for remote file reads/writes (e.g. `gbk`) |
|
|
245
|
+
| `fileReference` | bool | `true` | remote `@` completion: in a remote session `@` lists the **remote** tree over SFTP (issue #39); off → only the local mirror |
|
|
246
|
+
| `fileReferenceMaxResults` | int | `20` | max `@` candidates rendered for one query |
|
|
247
|
+
| `fileReferenceMaxEntries` | int | `3000` | max entries retained in one remote workspace's `@` index |
|
|
248
|
+
| `fileReferenceExcludedDirectories` | string[] | `[.git, node_modules, dist, build, out, coverage, target, .next, .nuxt, .turbo, .venv, __pycache__, .pytest_cache, .mypy_cache, .gradle]` | directory basenames the remote `@` traversal skips |
|
|
249
|
+
| `fileReferenceTimeoutMs` | int | `4000` | wall-clock budget for one remote `@` index pass (on expiry the partial index answers rather than making the caret wait) |
|
|
240
250
|
|
|
241
251
|
## FAQ / troubleshooting
|
|
242
252
|
|
|
253
|
+
**`@` lists remote files but the built-in read tool cannot open them** — the harness's own file tools see the session's **local mirror** (`$DSH_HOME/remote-workspaces/…`), which stays empty until `rw_sync` downloads it. Read remote files with `rw_read_file` / the sidebar remote tab: `@src/main.c` in a remote session means `<remote workspace>/src/main.c`, and every `rw_*` tool resolves such a relative path against the remote workspace root. Seeing nothing at all? The remote `@` index falls back to the mirror when the host is unreachable, and the settings page's 测试连接 shows why.
|
|
254
|
+
|
|
243
255
|
**Host key 变了 / 提示可能中间人** — 主机重装过或密钥更换过:`/remote-forget-key`(或设置页 → 机器 → 重新信任),下次连接重新记录。
|
|
244
256
|
|
|
245
257
|
**连接报"认证失败"** — 检查用户名/密码/私钥路径;私钥加密了要填 Passphrase;公司机器要求 OTP/动态码时勾选 keyboard-interactive。
|
|
@@ -264,6 +276,21 @@ Giving the plugin a machine's credentials lets the agent run **shell commands as
|
|
|
264
276
|
|
|
265
277
|
MIT
|
|
266
278
|
|
|
279
|
+
## Contributing
|
|
280
|
+
|
|
281
|
+
Contributions are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md). Questions, setups and "is this supported?" go to [Discussions](https://github.com/flymysql/dsh-remote/discussions); reproducible bugs go to [Issues](https://github.com/flymysql/dsh-remote/issues).
|
|
282
|
+
|
|
283
|
+
Thanks to everyone who has landed a change here (merged PRs in parentheses):
|
|
284
|
+
|
|
285
|
+
[@dahaipeng](https://github.com/dahaipeng) (#31) ·
|
|
286
|
+
[@YiHui-Liu](https://github.com/YiHui-Liu) (#28) ·
|
|
287
|
+
[@nekomona](https://github.com/nekomona) (#24) ·
|
|
288
|
+
[FoolishWiser](https://github.com/FoolishWiser) (#17) ·
|
|
289
|
+
[@jace1cch](https://github.com/jace1cch) (#16) ·
|
|
290
|
+
[@Minggle](https://github.com/Minggle) (#10) ·
|
|
291
|
+
[4FMTWRV](https://github.com/4FMTWRV) (#6) ·
|
|
292
|
+
[glzhangzhi](https://github.com/glzhangzhi) (per-session SSH pool fix)
|
|
293
|
+
|
|
267
294
|
## Changelog
|
|
268
295
|
|
|
269
296
|
See [CHANGELOG.md](./CHANGELOG.md).
|
package/README.zh.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
# dsh-remote
|
|
6
6
|
|
|
7
|
+
由 [@flymysql](https://github.com/flymysql) 维护 · [博客](https://gitpull.cn) · [讨论区](https://github.com/flymysql/dsh-remote/discussions) · [Issue](https://github.com/flymysql/dsh-remote/issues) · [English](./README.md)
|
|
8
|
+
|
|
7
9
|
## 官方 Desktop 兼容适配(实验性,尚未发布)
|
|
8
10
|
|
|
9
11
|
本分支增加对 [DeepSeek 官方 Desktop](https://github.com/deepseek-ai/deepseek-harness)
|
|
@@ -26,6 +28,8 @@ Desktop 安装器还可能要求明确配置 `ssh2` / `cpu-features` 可选构
|
|
|
26
28
|
隔离验证中禁用了这些可选脚本;本改动不放宽应用的构建白名单,也不自动批准脚本。
|
|
27
29
|
|
|
28
30
|
[](https://www.npmjs.com/package/dsh-remote)
|
|
31
|
+
[](https://www.npmjs.com/package/dsh-remote)
|
|
32
|
+
[](https://www.npmjs.com/package/dsh-remote)
|
|
29
33
|
[](LICENSE)
|
|
30
34
|
[](https://github.com/topics/dsh-plugin)
|
|
31
35
|
|
|
@@ -56,12 +60,13 @@ DSH 的 Web 界面刻意只监听 `127.0.0.1`(CLI 为安全拒绝 `--host 0.0.
|
|
|
56
60
|
## 功能
|
|
57
61
|
|
|
58
62
|
- **多机 SSH** —— 可存任意多台主机(host/port/user + **私钥**或**密码**)。密码只存在本地,界面不回显;在设置里一键切当前机。每机可配 passphrase / 主机指纹策略 / SSH agent / keyboard-interactive(OTP)/ 跳板机,以及可选的 **系统钥匙串加密密码**。
|
|
59
|
-
- **`~/.ssh/config`
|
|
63
|
+
- **`~/.ssh/config` 别名(实时解析,不存副本)** —— 机器可以只保存一个 **Host 别名**(`useSshConfig`):主机名/用户/端口/私钥/跳板机**每次连接都从 `~/.ssh/config` 实时解析**,改配置立刻生效、无需重新导入;注册表里**不存这些值的副本**(私钥只引用路径,永不读内容)。支持 OpenSSH 语义:`Host a b` 多别名、`*`/`?` 通配、`!` 取反、`Include`(含通配、相对 `~/.ssh`)、行尾 `\` 续行、以及 ssh_config(5) 的**首个取值优先**规则。设置页「从 ~/.ssh/config 导入」列表里点别名即按别名保存(也可以「复制字段」成普通机器);列表与机器行都会显示 **别名 → 实际解析到哪台机**,`ProxyJump` 多跳、`ProxyCommand` 等插件无法照做的事会**显式告警**而不是静默降级。
|
|
60
64
|
- **双 tab 工作区选择器**(填充原生「Add workspace」流程):
|
|
61
65
|
- **本机** —— 走 **host 端原生系统文件夹对话框**选本地目录(或直接输入本地路径)→ 直接成为普通 DSH 本地工作区(与本地工作区共存)。优先用 DSH 的 `directoryPicker` 服务,服务缺失时**回退到插件自持的原生选择器**(macOS `osascript` / Linux `zenity`→`kdialog`)——桌面启动路径上框架服务不注册也能用。
|
|
62
66
|
- **远程** —— 选择器是**居中弹窗**(窄侧边栏也不会被挤压)。先**选机器** → Windows 主机根级显示 **「此电脑」多盘视图**(`C:\`、`D:\`、`E:\`…,而不是 Git Bash 的 MSYS 根),路径框**实时补全**目录(支持 `C:\Users\…` 或 `/c/Users/…` 任意写法,Windows 路径在底层自动改写为 Git Bash 形式);**选中一个目录立即列出它下一级**(OS/VSCode 式级联)。另有 **「浏览…」文件选择式浮层**(Windows 面包屑 `此电脑 / C:\ / Users / dev` 可点击跳级、驱动器行、大小/时间、跟随软链),选中**回填输入框不提交**,你复核/修改后再确定;「回上一级」任意深度可用(包括浮层直接打开在路径栏当前路径时)。**最近工作区**快捷入口、**`~` 主目录**、**新建目录**一键可达。确定会创建**真实本地镜像**(`$DSH_HOME/remote-workspaces/<host>-<user>-<port>/<basename>`;仅当同主机上**别的远端路径**已占用同名 basename 时才追加短路径 hash)→ harness 把它当真实工作区收养,同时 dsh-remote 通过 SFTP 保持同步。所选工作区会**持久化到该机器**,重启不丢。
|
|
63
67
|
- **Git Bash 默认终端(Windows 主机)** —— 自动探测远程平台(`cmd /c ver`,附 `uname -s` 的 MINGW/MSYS 探测兜底);Windows 机器自动定位 Git Bash(`config.shell` 可显式指定或 `native` 关闭),所有命令经 `bash -s` 从 SSH 通道 stdin 管道执行,不依赖 cmd/PowerShell,也不受引号/反斜杠转义困扰;`rw_exec` 默认在 Git Bash 形式的 cwd(`/c/Users/…`)下执行。`/dsh-remote/status`、`rw_info`、设置页「测试连接」都会报告检测到的平台与 shell。
|
|
64
68
|
- **Windows 路径自动改写** —— 用户输入 `C:\Users\dev\project`(或 `C:/…`、`/c/…`、`/C:/…`)时底层自动规范为 Git Bash 形式 `/c/Users/dev/project` 执行;工作区存储与展示为 Windows 形式 `C:\Users\dev\project`。模型工具全部接受并展示两种写法;SFTP 访问使用 Win32-OpenSSH 的 `/D:/…` 形式(见 `toSftpPath`)。
|
|
69
|
+
- **远程 `@` 补全(issue #39)** —— 远程会话里输入 `@` 会**列出远端目录树**(走 SFTP 实时读,不是本地镜像)。目录逐级下钻、无斜杠时在整棵树上模糊匹配,候选是**相对远程工作区根的路径**(`@src/main.c`),与本地会话的写法一致;`rw_*` 工具接受这种相对路径并自动拼到远程工作区根上。索引有预算保护(条目/目录/时限 + 缓存 + 失败熔断),**远端不可达时自动回退到本地镜像**(不会静默变成空列表)。本地会话完全不受影响。
|
|
65
70
|
- **双向 SFTP 同步(三路冲突检测)** —— `rw_sync`(远程→镜像)、`rw_push`(镜像→远程)。两边都改过的文件会列出冲突、绝不静默覆盖(`force=true` 覆盖)。默认 **深度 8 / 2000 文件**,触顶会标明 **`TRUNCATED`**。支持 dry-run、后台任务、gitignore 风格 ignore 规则。
|
|
66
71
|
- **模型工具(20 个)** —— `rw_info`、`rw_connect`、`rw_pick_workspace`、`rw_list_dir`、`rw_stat`、`rw_read_file`(utf-8/gbk)、`rw_write_file`、`rw_edit`(字面替换 + mtime 乐观锁)、`rw_append`、`rw_mkdir`、`rw_remove`、`rw_move`、`rw_exec`、`rw_search`(POSIX 优先 rg/grep,否则 SFTP 遍历)、`rw_download`/`rw_upload`、`rw_forward`、`rw_sync`、`rw_push`、`rw_disconnect`。
|
|
67
72
|
- **端口转发面板** —— 设置页或 `rw_forward` 创建/启停本地与反向隧道。
|
|
@@ -212,6 +217,11 @@ scripts/dev-run.sh --status # 是否在运行
|
|
|
212
217
|
| `connectTimeoutMs` | int | 15000 | SSH 连接超时 |
|
|
213
218
|
| `maxFileBytes` | int | 52428800 | 镜像同步时跳过超过该大小的文件(0=不设上限) |
|
|
214
219
|
| `hostKeyMode` | string | `accept-new` | 主机指纹策略:`accept-new`(首次信任)、`verify`(拒绝未知主机)、`off`(跳过校验) |
|
|
220
|
+
| `fileReference` | bool | `true` | 远程 `@` 补全:远程会话的 `@` 列出**远端**目录树(issue #39);关闭则只有本地镜像 |
|
|
221
|
+
| `fileReferenceMaxResults` | int | `20` | 一次 `@` 查询最多返回多少候选 |
|
|
222
|
+
| `fileReferenceMaxEntries` | int | `3000` | 一棵远程工作区索引最多保留多少条目 |
|
|
223
|
+
| `fileReferenceExcludedDirectories` | string[] | `[.git, node_modules, dist, build, out, coverage, target, .next, .nuxt, .turbo, .venv, __pycache__, .pytest_cache, .mypy_cache, .gradle]` | 远程 `@` 遍历跳过的目录名 |
|
|
224
|
+
| `fileReferenceTimeoutMs` | int | `4000` | 一次远程索引遍历的墙钟预算(超时用已扫到的部分结果,不让光标等) |
|
|
215
225
|
|
|
216
226
|
## 安全提醒
|
|
217
227
|
|
|
@@ -221,6 +231,21 @@ scripts/dev-run.sh --status # 是否在运行
|
|
|
221
231
|
|
|
222
232
|
MIT
|
|
223
233
|
|
|
234
|
+
## 参与贡献
|
|
235
|
+
|
|
236
|
+
欢迎贡献,请先阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)。使用问题、环境配置、「支持 XX 吗」这类讨论请走 [讨论区](https://github.com/flymysql/dsh-remote/discussions);可复现的缺陷请提 [Issue](https://github.com/flymysql/dsh-remote/issues)。
|
|
237
|
+
|
|
238
|
+
感谢以下已合并 PR 的贡献者:
|
|
239
|
+
|
|
240
|
+
[@dahaipeng](https://github.com/dahaipeng) (#31) ·
|
|
241
|
+
[@YiHui-Liu](https://github.com/YiHui-Liu) (#28) ·
|
|
242
|
+
[@nekomona](https://github.com/nekomona) (#24) ·
|
|
243
|
+
[FoolishWiser](https://github.com/FoolishWiser) (#17) ·
|
|
244
|
+
[@jace1cch](https://github.com/jace1cch) (#16) ·
|
|
245
|
+
[@Minggle](https://github.com/Minggle) (#10) ·
|
|
246
|
+
[4FMTWRV](https://github.com/4FMTWRV) (#6) ·
|
|
247
|
+
[glzhangzhi](https://github.com/glzhangzhi)(per-session SSH 连接池修复)
|
|
248
|
+
|
|
224
249
|
## 变更记录
|
|
225
250
|
|
|
226
251
|
见 [CHANGELOG.md](./CHANGELOG.md)。
|
package/lib/binding.js
CHANGED
|
@@ -38,9 +38,11 @@ const isUnder = (base, dir) => base === dir || base.startsWith(dir + path.sep) |
|
|
|
38
38
|
*
|
|
39
39
|
* @param {string} local - absolute local path, typically a session's cwd.
|
|
40
40
|
* @param {string} root - the mirror registry root (`$DSH_HOME/remote-workspaces`).
|
|
41
|
-
* @returns {{mirrorDir: string|null, remotePath: string, machine: {host: string, port: number, username: string}|null}}
|
|
42
|
-
* `machine` is the mirror-recorded origin
|
|
43
|
-
*
|
|
41
|
+
* @returns {{mirrorDir: string|null, remotePath: string, machine: {host: string, port: number, username: string, alias: string}|null}}
|
|
42
|
+
* `machine` is the mirror-recorded origin (`alias` is the ~/.ssh/config alias
|
|
43
|
+
* it was created through, '' when the machine uses literal values), or null
|
|
44
|
+
* when `local` is not inside a usable mirror (in which case `remotePath` is
|
|
45
|
+
* empty too).
|
|
44
46
|
*/
|
|
45
47
|
export function resolveMirror(local, root) {
|
|
46
48
|
let mirrorDir = null
|
|
@@ -79,6 +81,10 @@ export function resolveMirror(local, root) {
|
|
|
79
81
|
host: String(meta.host),
|
|
80
82
|
port: Number(meta.port) || 22,
|
|
81
83
|
username: String(meta.username || ''),
|
|
84
|
+
// The ~/.ssh/config alias this mirror was created through, when the
|
|
85
|
+
// machine was saved as one (issue #38): it keeps the mirror bound to
|
|
86
|
+
// its machine even if that alias's HostName/port later change.
|
|
87
|
+
alias: String(meta.alias || ''),
|
|
82
88
|
}
|
|
83
89
|
} catch { /* unparsable meta → not a usable binding */ }
|
|
84
90
|
}
|