@rjfan/dsh-presence 0.0.0-stage → 0.18.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 +21 -0
- package/README.md +263 -2
- package/README.zh-CN.md +250 -0
- package/client.js +1 -0
- package/cordis.patch.yml +38 -0
- package/icon.svg +30 -0
- package/index.js +322 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +81 -4
- package/src/api.js +1277 -0
- package/src/core/connection.mjs +1806 -0
- package/src/core/ssh-config.mjs +158 -0
- package/src/custom-hosts.js +311 -0
- package/src/debug.js +21 -0
- package/src/destructive.js +193 -0
- package/src/diff.js +210 -0
- package/src/file-references.js +493 -0
- package/src/fs-provider.js +449 -0
- package/src/fs-selfcheck.js +135 -0
- package/src/intercept.js +133 -0
- package/src/job-notifier.js +193 -0
- package/src/localpaths.js +39 -0
- package/src/mounts.js +131 -0
- package/src/observed-store.js +78 -0
- package/src/path-ops.js +390 -0
- package/src/pool.js +818 -0
- package/src/port-discovery.js +278 -0
- package/src/remote-search.js +426 -0
- package/src/shared.js +14 -0
- package/src/tail.js +190 -0
- package/src/tools.js +2838 -0
- package/src/tunnel-store.js +278 -0
- package/src/workspaces.js +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fanrj3
|
|
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
|
@@ -1,3 +1,264 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="assets/icon-180.png" width="88" alt="Presence icon" />
|
|
4
|
+
|
|
5
|
+
# Presence
|
|
6
|
+
|
|
7
|
+
**Your SSH hosts, as local tabs.**
|
|
8
|
+
|
|
9
|
+
Persistent remote workspaces, native file editing, search, exec, terminals and port
|
|
10
|
+
forwards for coding agents — with **nothing installed on the remote host**.
|
|
11
|
+
|
|
12
|
+
[English](README.md) · [简体中文](README.zh-CN.md) · [Releases](docs/RELEASE.md) · [MCP server](mcp/README.md)
|
|
13
|
+
|
|
14
|
+
[](https://www.npmjs.com/package/@rjfan/dsh-presence)
|
|
15
|
+
[](https://www.npmjs.com/package/presence-mcp)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
Running an agent on every remote machine means installing it N times, upgrading it N
|
|
28
|
+
times and configuring proxies per host. Running `ssh host '<command>'` from the local
|
|
29
|
+
agent means a new handshake per command, no native file tools and no continuity.
|
|
30
|
+
|
|
31
|
+
Presence keeps the agent local and **extends its reach through one persistent SSH
|
|
32
|
+
connection per host**. The remote side is still plain `sshd`: SFTP, exec and a PTY.
|
|
33
|
+
|
|
34
|
+
| Approach | Cost |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Install the agent on every host | N installs, N upgrades, per-host proxying |
|
|
37
|
+
| `ssh host '<command>'` per call | Handshake per command, no file tools, no sessions |
|
|
38
|
+
| **Presence** | One connection per host, native tools, nothing installed remotely |
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Remote zero-install** — uses only `sshd` built-ins: the SFTP subsystem, exec channels and a PTY.
|
|
43
|
+
- **One handshake** — a pool keeps one connection per host; every later call is a request on it.
|
|
44
|
+
- **Native remote tools** — `rg`/`grep` runs on the target, commands run in the target shell; path, permission and encoding semantics are the remote ones.
|
|
45
|
+
- **Local-like edits** — atomic writes, mode preservation, ambiguity refusal, read-before-write guard, `presence_edit` with dry-run diffs.
|
|
46
|
+
- **Real workspaces** — a remote directory becomes a normal DSH workspace with a file tree and terminals that open **on the remote machine**.
|
|
47
|
+
- **Recoverable deletes** — destructive shell commands dry-run first; `presence_trash` moves paths to a remote trash with restore.
|
|
48
|
+
- **`@` completes remote files** — inside a remote workspace, file-reference completion indexes the machine itself (`rg --files`, `find` fallback), not a local mirror.
|
|
49
|
+
- **File operations, not just reads** — `presence_mkdir` / `move` / `copy` / `chmod` plus a right-click menu in the file tree; deletes go to the recoverable trash.
|
|
50
|
+
- **Diff and live logs** — unified diff between two remote files or a remote file and a local one; follow a log file from the panel without keeping a `tail -f` job around.
|
|
51
|
+
- **Ports that come back** — save forwards with auto-restore on reconnect, and let the Ports panel discover newly listening ports and forward them with one click.
|
|
52
|
+
- **Job notifications** — a toast (and desktop notification) when a background job finishes, plus an optional local `notifyCommand` for daemons and phones.
|
|
53
|
+
- **Two shells, one kernel** — the DSH plugin and a standalone MCP server (`presence-mcp`, 20 core tools) share the same kernel.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
### 1. Install the DSH plugin
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
dsh plugin add @rjfan/dsh-presence@0.18.0
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
From a source checkout:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
dsh plugin add /path/to/dsh-presence
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. Pick a host and open a workspace
|
|
70
|
+
|
|
71
|
+
Open **Settings → Presence → New**, choose a host from `~/.ssh/config` (or paste a
|
|
72
|
+
manual `ssh` command), then pick a remote folder. It appears as a workspace; the
|
|
73
|
+
left sidebar, file tree, terminal and agent tools all use that remote directory.
|
|
74
|
+
|
|
75
|
+
### 3. Work
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
You: 把远程 8000 端口转发到本地,然后打开看看
|
|
79
|
+
Agent: presence_forward { remotePort: 8000 }
|
|
80
|
+
→ http://127.0.0.1:8000
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## MCP server
|
|
84
|
+
|
|
85
|
+
The same kernel is available to agents that cannot install DSH plugins (Codex CLI,
|
|
86
|
+
Claude Code, OpenCode, Cursor, …).
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
npx -y presence-mcp # 20 tools over stdio
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```jsonc
|
|
93
|
+
// Claude Desktop / OpenCode style
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"presence": {
|
|
97
|
+
"command": "npx",
|
|
98
|
+
"args": ["-y", "presence-mcp"],
|
|
99
|
+
"env": {
|
|
100
|
+
"PRESENCE_HOST": "server",
|
|
101
|
+
"PRESENCE_WORKSPACE": "server:/srv/app"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See [mcp/README.md](mcp/README.md) for Codex TOML, Claude Code, OpenCode and
|
|
109
|
+
Cursor/Windsurf examples.
|
|
110
|
+
|
|
111
|
+
## How it works
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
┌───────────────────────────────┐
|
|
115
|
+
│ DSH plugin / MCP server │ two shells, one kernel
|
|
116
|
+
└──────────────┬────────────────┘
|
|
117
|
+
│
|
|
118
|
+
Presence kernel
|
|
119
|
+
├── ssh-config hosts, ports, users, keys, jumps from ~/.ssh/config
|
|
120
|
+
├── connection one persistent connection: SFTP / exec / PTY + pool + cancel
|
|
121
|
+
└── host pool one connection per host, created on demand, idle-collected
|
|
122
|
+
│
|
|
123
|
+
SSH (your existing keys, known_hosts, jumps, proxies)
|
|
124
|
+
│
|
|
125
|
+
target: Linux / macOS, stock sshd
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Agent operation | Channel | Remote dependency |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| read / write / edit / list / stat | SFTP subsystem | `sftp-server` (stock sshd) |
|
|
131
|
+
| exec, search (`rg`/`grep`), git | exec channel | commands already on the host |
|
|
132
|
+
| interactive terminal | shell channel + PTY | the host's login shell |
|
|
133
|
+
|
|
134
|
+
Measured on a Linux host:
|
|
135
|
+
|
|
136
|
+
| Operation | Latency |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `ssh host '<cmd>'` (new connection every time) | 520–540 ms |
|
|
139
|
+
| Presence connect (one handshake, key auth) | 145 ms |
|
|
140
|
+
| Presence `sftp stat` | p50 0.6 ms / p95 0.9 ms |
|
|
141
|
+
| Presence `exec true` (new channel + shell) | p50 7.6 ms |
|
|
142
|
+
| 8 concurrent exec channels | 15 ms total |
|
|
143
|
+
|
|
144
|
+
> Keep TCP_NODELAY on: without it, request/response hits delayed ACK and p95 goes
|
|
145
|
+
> from 0.9 ms to 48 ms.
|
|
146
|
+
|
|
147
|
+
## Tools
|
|
148
|
+
|
|
149
|
+
The DSH plugin exposes 24 tools. Relative paths resolve against the active remote
|
|
150
|
+
workspace; `host` is optional inside a workspace.
|
|
151
|
+
|
|
152
|
+
<details>
|
|
153
|
+
<summary><b>Show all tools</b></summary>
|
|
154
|
+
|
|
155
|
+
| Tool | What it does |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `presence_hosts` | List SSH hosts, connection state and default host (`format:"json"` supported). |
|
|
158
|
+
| `presence_connect` | Open/reuse a host connection and report the remote identity. |
|
|
159
|
+
| `presence_exec` | Run a remote command; `pipefail`, `background`, `waitForPort`, structured JSON, `script`, `saveStdout`; destructive commands dry-run first. |
|
|
160
|
+
| `presence_jobs` | Manage background jobs: `list`, `status`, `wait` (with `timedOut`), `stop` (with `alive`/`verified`/`portReleased`), `purge`. |
|
|
161
|
+
| `presence_read` | Read a remote file with line numbers, `offset`/`limit`, or `raw:true` for verbatim content. |
|
|
162
|
+
| `presence_read_many` | Read 1–50 remote files in one call, with per-file byte/line caps. |
|
|
163
|
+
| `presence_write` | Atomic write; preserves mode; returns real mode, size and full sha256; `ifMatchSha256` optimistic lock. |
|
|
164
|
+
| `presence_edit` | Exact-text replacement or line-range replacement, with compact diff, `dryRun`, JSON fields. |
|
|
165
|
+
| `presence_ls` | List a directory; `recursive`, `totalBytes`, JSON output. |
|
|
166
|
+
| `presence_stat` | Batch `stat` for 1–100 paths: type, size, mode, mtime, optional sha256; missing paths return `exists:false`. |
|
|
167
|
+
| `presence_mkdir` | Create a remote directory (recursive by default); idempotent when it already exists. |
|
|
168
|
+
| `presence_move` | Rename or move a path inside the same machine; refuses to replace an existing file unless `overwrite:true`. |
|
|
169
|
+
| `presence_copy` | Copy a remote file (SFTP stream, or `cp -p` for large files) or a directory with `recursive:true`. |
|
|
170
|
+
| `presence_chmod` | Change permissions with an octal mode (`755`, `0644`), optionally recursive. |
|
|
171
|
+
| `presence_diff` | Unified diff of two remote files, or a remote file against a local one; context lines and JSON stats. |
|
|
172
|
+
| `presence_tail` | Read the last N lines of a remote file, or follow it for a bounded time (`follow:true`, `timeoutMs`). |
|
|
173
|
+
| `presence_search` | Remote `rg`/`grep` search, count-only mode, JSON matches. |
|
|
174
|
+
| `presence_glob` | Find remote files by glob with size/mtime. |
|
|
175
|
+
| `presence_import` | Local → remote binary-safe upload with mode and sha256. |
|
|
176
|
+
| `presence_export` | Remote → local real file (content-addressed), for preview or native apps. |
|
|
177
|
+
| `presence_forward` | Forward a remote port to `127.0.0.1`; open/list/close with JSON. |
|
|
178
|
+
| `presence_port_probe` | Check listeners and TCP reachability for up to 20 ports; `expect:open|closed`, polling. |
|
|
179
|
+
| `presence_http_probe` | Wait for port → forward → local HTTP assertions → close → verify the local port was released. |
|
|
180
|
+
| `presence_terminal` | Persistent PTY: `open` with `cwd`/`command`, `write` (`ackOnly`), `read`, `close`; tmux-backed sessions survive reconnects. |
|
|
181
|
+
| `presence_trash` | Recoverable delete: move to remote trash, list/restore; batch `paths[]`; returns `sourceRemoved` and `restoreHint`. |
|
|
182
|
+
|
|
183
|
+
</details>
|
|
184
|
+
|
|
185
|
+
## Safety and recovery
|
|
186
|
+
|
|
187
|
+
- **Destructive commands never run unconfirmed.** `rm -r/-f`, `git clean -fdx`,
|
|
188
|
+
`find -delete`, `shred`, `truncate` and `dd of=` are first replayed with
|
|
189
|
+
no-op shims to expand their real targets; unset variables and protected paths
|
|
190
|
+
(workspace root, home, `/`, `.git`) are refused. A second call with
|
|
191
|
+
`confirm:true` is required to execute.
|
|
192
|
+
- **Prefer `presence_trash`** for anything under a workspace: it moves the path to
|
|
193
|
+
`~/.dsh/presence-trash/<timestamp>/` and returns a restore hint.
|
|
194
|
+
- **Writes are atomic** and keep the original file mode; `presence_edit` refuses
|
|
195
|
+
ambiguous anchors instead of guessing.
|
|
196
|
+
- **Read before write**: built-in `read`/`write`/`edit` share a read-before-write
|
|
197
|
+
guard; `presence_write` records the observation so both APIs can be mixed.
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
The plugin reads these environment variables on the host process:
|
|
202
|
+
|
|
203
|
+
| Variable | Default | Meaning |
|
|
204
|
+
| --- | --- | --- |
|
|
205
|
+
| `PRESENCE_HOST` | — | Default SSH host (alias from `~/.ssh/config` or a manual host). |
|
|
206
|
+
| `PRESENCE_WORKSPACE` | — | `host:/abs/path` to bind a remote workspace. |
|
|
207
|
+
| `PRESENCE_PASSWORD_<ALIAS>` | — | Password for a host; `<ALIAS>` is uppercase with non-alphanumerics replaced by `_`. |
|
|
208
|
+
| `PRESENCE_MAX_CHANNELS` | 8 | Concurrent channels per connection. |
|
|
209
|
+
| `PRESENCE_CONNECT_TIMEOUT_MS` | 25000 | Connection attempt timeout. |
|
|
210
|
+
| `PRESENCE_IDLE_TIMEOUT_MS` | 900000 | Idle connection timeout (connections with terminals are kept). |
|
|
211
|
+
| `PRESENCE_READ_ONLY` | false | Refuse writes/edits; reads, search and exec still work. |
|
|
212
|
+
|
|
213
|
+
Full configuration and the path/permission matrix are in
|
|
214
|
+
[docs/VERIFICATION.md](docs/VERIFICATION.md) and [docs/HANDOFF.md](docs/HANDOFF.md).
|
|
215
|
+
|
|
216
|
+
## Development
|
|
217
|
+
|
|
218
|
+
```sh
|
|
219
|
+
npm install
|
|
220
|
+
npm install --prefix .dev --ignore-scripts rollup @rollup/plugin-node-resolve \
|
|
221
|
+
@rollup/plugin-commonjs @rollup/plugin-terser codemirror @codemirror/language-data \
|
|
222
|
+
@codemirror/theme-one-dark
|
|
223
|
+
node scripts/build-client.mjs
|
|
224
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.18.0
|
|
225
|
+
# → dist/presence-plugin-0.18.0.zip
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Tests require one SSH-reachable host:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
node scripts/regression.mjs server # transport, symlinks, timeouts, binary
|
|
232
|
+
node scripts/job-probe.mjs server # jobs + port forwarding
|
|
233
|
+
node scripts/feature-probe.mjs server # wait, sessions, pgid, readiness, search
|
|
234
|
+
node scripts/import-probe.mjs server # local→remote binary, mode, sha256
|
|
235
|
+
node scripts/safety-probe.mjs server # destructive guard + observation store
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Known limits
|
|
239
|
+
|
|
240
|
+
- The mount path (`~/.dsh/presence/mounts/...`) is virtual: it works through
|
|
241
|
+
`ctx.fs` / agent tools, but native apps (Explorer, Office, host Python) cannot
|
|
242
|
+
see it — use `presence_export` first.
|
|
243
|
+
- SSH `MaxSessions` (default 10) bounds concurrent channels; the pool uses at most 8.
|
|
244
|
+
- PTY terminals follow the connection; cross-reconnect shells need `tmux` on the target
|
|
245
|
+
(used automatically by `presence_terminal { session }`).
|
|
246
|
+
- ProxyJump/ProxyCommand are parsed and warned about, but not used for dialing yet.
|
|
247
|
+
- `@` file completion indexes the remote tree lazily (about 10 s per workspace);
|
|
248
|
+
on a very large tree the first query can take a few seconds.
|
|
249
|
+
- Port auto-restore runs when the connection is re-established; a forward whose
|
|
250
|
+
saved local port is already taken falls back to another free port.
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
Issues and PRs are welcome. For a feature, open an issue describing the remote
|
|
255
|
+
workflow you want; for a fix, keep the diff focused and add a probe under
|
|
256
|
+
`scripts/` when behavior changes.
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
[MIT](LICENSE)
|
|
261
|
+
|
|
262
|
+
## Star History
|
|
263
|
+
|
|
264
|
+
[](https://star-history.com/#fanrj3/dsh-presence&Date)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/icon-180.png" width="88" alt="Presence icon" />
|
|
4
|
+
|
|
5
|
+
# Presence
|
|
6
|
+
|
|
7
|
+
**把 SSH 机器变成本地标签页。**
|
|
8
|
+
|
|
9
|
+
给 coding agent 用的持久远程工作区、原生文件编辑、搜索、命令执行、终端和端口转发
|
|
10
|
+
—— **远端不需要安装任何东西**。
|
|
11
|
+
|
|
12
|
+
[English](README.md) · [简体中文](README.zh-CN.md) · [版本记录](docs/RELEASE.md) · [MCP server](mcp/README.md)
|
|
13
|
+
|
|
14
|
+
[](https://www.npmjs.com/package/@rjfan/dsh-presence)
|
|
15
|
+
[](https://www.npmjs.com/package/presence-mcp)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 为什么不是「agent 跑在远端」
|
|
26
|
+
|
|
27
|
+
在每台服务器上装 agent,意味着装 N 次、升级 N 次、逐台配代理;让本地 agent 每次
|
|
28
|
+
`ssh host '<command>'`,意味着每条命令一次握手、用不上原生文件工具、上下文不连续。
|
|
29
|
+
|
|
30
|
+
Presence 让 agent 留在本机,**通过每台机器一条常驻 SSH 连接把能力伸到远端**。远端
|
|
31
|
+
仍然只是普通的 `sshd`:SFTP、exec 和一个 PTY。
|
|
32
|
+
|
|
33
|
+
| 方案 | 代价 |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| 每台机器装 agent | N 次安装、N 次升级、逐台代理配置 |
|
|
36
|
+
| 每个操作 `ssh host '<cmd>'` | 每条命令一次握手、无文件工具、无会话 |
|
|
37
|
+
| **Presence** | 每台机器一条连接、原生工具、远端零安装 |
|
|
38
|
+
|
|
39
|
+
## 特性
|
|
40
|
+
|
|
41
|
+
- **远端零安装** —— 只用 `sshd` 自带能力:SFTP 子系统、exec 通道和 PTY。
|
|
42
|
+
- **一次握手** —— 连接池为每台机器保持一条连接,之后的调用都是这条连接上的请求。
|
|
43
|
+
- **用目标机的原生工具** —— `rg`/`grep` 在目标机跑,命令在目标 shell 里执行;路径、权限、编码都是远端的语义。
|
|
44
|
+
- **接近本地的编辑** —— 原子写、保留 mode、锚点歧义拒绝、读后写守卫、`presence_edit` 的 dry-run diff。
|
|
45
|
+
- **真正的远程工作区** —— 远端目录注册成普通 DSH 工作区,文件树、终端都**开在远端机器上**。
|
|
46
|
+
- **可恢复的删除** —— 危险 shell 命令先 dry-run;`presence_trash` 移入远端回收站并可恢复。
|
|
47
|
+
- **`@` 能补全远端文件** —— 远程工作区里,文件引用补全直接索引目标机(`rg --files`,回退 `find`),不是本地镜像。
|
|
48
|
+
- **文件操作不只是读** —— `presence_mkdir` / `move` / `copy` / `chmod`,文件树右键菜单直接操作;删除走可恢复回收站。
|
|
49
|
+
- **Diff 与实时日志** —— 两个远端文件、或远端与本地文件之间的统一 diff;面板里直接跟随日志,不用常驻 `tail -f` 任务。
|
|
50
|
+
- **转发会自己回来** —— 保存转发并自动恢复;端口面板发现新监听端口后可一键转发。
|
|
51
|
+
- **任务完成通知** —— 后台任务结束时弹 toast(及桌面通知),也可配置本地 `notifyCommand` 推送到手机或守护进程。
|
|
52
|
+
- **两种外壳,同一个内核** —— DSH 插件与独立 MCP server(`presence-mcp`,20 个核心工具)共用同一套内核。
|
|
53
|
+
|
|
54
|
+
## 快速开始
|
|
55
|
+
|
|
56
|
+
### 1. 安装 DSH 插件
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
dsh plugin add @rjfan/dsh-presence@0.18.0
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
从源码安装:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
dsh plugin add /path/to/dsh-presence
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. 选一台机器,打开工作区
|
|
69
|
+
|
|
70
|
+
打开 **设置 → Presence → 新建**,从 `~/.ssh/config` 选一台机器(或直接粘贴一条
|
|
71
|
+
`ssh` 命令),再选一个远端目录。它会以工作区出现:左侧文件树、终端和 agent 工具
|
|
72
|
+
都使用这个远端目录。
|
|
73
|
+
|
|
74
|
+
### 3. 开始用
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
你:把远程 8000 端口转发到本地,然后打开看看
|
|
78
|
+
Agent: presence_forward { remotePort: 8000 }
|
|
79
|
+
→ http://127.0.0.1:8000
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## MCP server
|
|
83
|
+
|
|
84
|
+
同样的能力也提供给装不了 DSH 插件的 agent(Codex CLI、Claude Code、OpenCode、Cursor 等)。
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npx -y presence-mcp # 20 个工具,走 stdio
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```jsonc
|
|
91
|
+
// Claude Desktop / OpenCode 风格
|
|
92
|
+
{
|
|
93
|
+
"mcpServers": {
|
|
94
|
+
"presence": {
|
|
95
|
+
"command": "npx",
|
|
96
|
+
"args": ["-y", "presence-mcp"],
|
|
97
|
+
"env": {
|
|
98
|
+
"PRESENCE_HOST": "server",
|
|
99
|
+
"PRESENCE_WORKSPACE": "server:/srv/app"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Codex TOML、Claude Code、OpenCode、Cursor/Windsurf 的完整配置见
|
|
107
|
+
[mcp/README.md](mcp/README.md)。
|
|
108
|
+
|
|
109
|
+
## 工作原理
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
┌───────────────────────────────┐
|
|
113
|
+
│ DSH 插件 / MCP server │ 两种外壳,同一个内核
|
|
114
|
+
└──────────────┬────────────────┘
|
|
115
|
+
│
|
|
116
|
+
Presence 内核
|
|
117
|
+
├── ssh-config 从 ~/.ssh/config 读主机、端口、用户、密钥、跳板
|
|
118
|
+
├── connection 一条常驻连接:SFTP / exec / PTY + 通道池 + 取消
|
|
119
|
+
└── host pool 每台机器一条连接,按需建立、闲置回收
|
|
120
|
+
│
|
|
121
|
+
SSH(沿用你已有的密钥、known_hosts、跳板、代理)
|
|
122
|
+
│
|
|
123
|
+
目标机:Linux / macOS,原生 sshd
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Agent 操作 | 通道 | 远端依赖 |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| 读 / 写 / 编辑 / 列目录 / stat | SFTP 子系统 | sshd 自带 `sftp-server` |
|
|
129
|
+
| 执行命令、搜索(rg/grep)、git | exec 通道 | 目标机已有的命令 |
|
|
130
|
+
| 交互式终端 | shell 通道 + PTY | 目标机的登录 shell |
|
|
131
|
+
|
|
132
|
+
Linux 主机上的实测:
|
|
133
|
+
|
|
134
|
+
| 操作 | 延迟 |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `ssh host '<cmd>'`(每次新连接) | 520–540 ms |
|
|
137
|
+
| Presence 建连(一次握手,含密钥认证) | 145 ms |
|
|
138
|
+
| Presence `sftp stat` | p50 0.6 ms / p95 0.9 ms |
|
|
139
|
+
| Presence `exec true`(新通道 + shell) | p50 7.6 ms |
|
|
140
|
+
| 8 个 exec 通道并发 | 合计 15 ms |
|
|
141
|
+
|
|
142
|
+
> 必须保持 TCP_NODELAY:否则请求/响应会撞上 delayed ACK,p95 从 0.9 ms 劣化到 48 ms。
|
|
143
|
+
|
|
144
|
+
## 工具
|
|
145
|
+
|
|
146
|
+
DSH 插件提供 24 个工具。相对路径相对当前远程工作区;在工作区里 `host` 可省略。
|
|
147
|
+
|
|
148
|
+
<details>
|
|
149
|
+
<summary><b>展开全部工具</b></summary>
|
|
150
|
+
|
|
151
|
+
| 工具 | 说明 |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| `presence_hosts` | 列出 SSH 主机、连接状态和默认主机(支持 `format:"json"`)。 |
|
|
154
|
+
| `presence_connect` | 建立/复用连接并返回远端身份。 |
|
|
155
|
+
| `presence_exec` | 执行远端命令;`pipefail`、`background`、`waitForPort`、结构化 JSON、`script`、`saveStdout`;危险命令先 dry-run。 |
|
|
156
|
+
| `presence_jobs` | 后台任务:`list`、`status`、`wait`(含 `timedOut`)、`stop`(`alive`/`verified`/`portReleased`)、`purge`。 |
|
|
157
|
+
| `presence_read` | 读远端文件,带行号、`offset`/`limit`,`raw:true` 返回逐字节原样内容。 |
|
|
158
|
+
| `presence_read_many` | 一次读 1–50 个文件,支持每文件字节/行数上限。 |
|
|
159
|
+
| `presence_write` | 原子写;保留 mode;返回真实 mode、字节数和完整 sha256;`ifMatchSha256` 乐观锁。 |
|
|
160
|
+
| `presence_edit` | 精确文本替换或行号区间替换,带紧凑 diff、`dryRun` 和 JSON 字段。 |
|
|
161
|
+
| `presence_ls` | 列目录;`recursive`、`totalBytes`、JSON 输出。 |
|
|
162
|
+
| `presence_stat` | 批量 `stat` 1–100 个路径:类型、大小、mode、mtime、可选 sha256;缺失路径返回 `exists:false`。 |
|
|
163
|
+
| `presence_mkdir` | 创建远端目录(默认递归);已存在时幂等成功。 |
|
|
164
|
+
| `presence_move` | 同一台机器内重命名/移动;目标已存在时除非 `overwrite:true` 否则拒绝。 |
|
|
165
|
+
| `presence_copy` | 复制远端文件(SFTP 流,大文件走 `cp -p`),目录需 `recursive:true`。 |
|
|
166
|
+
| `presence_chmod` | 按八进制 mode 改权限(`755`/`0644`),可递归。 |
|
|
167
|
+
| `presence_diff` | 两个远端文件、或远端与本地文件的统一 diff;支持上下文行数和 JSON 统计。 |
|
|
168
|
+
| `presence_tail` | 读远端文件最后 N 行,或在限定时间内跟随(`follow:true`、`timeoutMs`)。 |
|
|
169
|
+
| `presence_search` | 远端 `rg`/`grep` 搜索,`countOnly`,JSON matches。 |
|
|
170
|
+
| `presence_glob` | 远端 glob 找文件,带 size/mtime。 |
|
|
171
|
+
| `presence_import` | 本地 → 远端二进制安全上传,可设 mode,返回 sha256。 |
|
|
172
|
+
| `presence_export` | 远端 → 本地真实文件(内容寻址),供预览或原生程序打开。 |
|
|
173
|
+
| `presence_forward` | 远端端口转发到 `127.0.0.1`;open/list/close 支持 JSON。 |
|
|
174
|
+
| `presence_port_probe` | 检查最多 20 个端口的监听与 TCP 可达性;`expect:open|closed` 可轮询。 |
|
|
175
|
+
| `presence_http_probe` | 等端口 → 开转发 → 本地 HTTP 断言 → 关转发 → 验证本地端口已释放。 |
|
|
176
|
+
| `presence_terminal` | 持久 PTY:`open` 可带 `cwd`/`command`,`write`(`ackOnly`)、`read`、`close`;tmux 会话可跨重连。 |
|
|
177
|
+
| `presence_trash` | 可恢复删除:移入远端回收站、list/restore;支持批量 `paths[]`;返回 `sourceRemoved` 和 `restoreHint`。 |
|
|
178
|
+
|
|
179
|
+
</details>
|
|
180
|
+
|
|
181
|
+
## 安全与恢复
|
|
182
|
+
|
|
183
|
+
- **危险命令不会直接执行。** `rm -r/-f`、`git clean -fdx`、`find -delete`、`shred`、
|
|
184
|
+
`truncate`、`dd of=` 会先用 no-op shim 重放以展开真实目标;未设置变量和受保护路径
|
|
185
|
+
(工作区根、家目录、`/`、`.git`)直接拒绝。必须再带 `confirm:true` 才会执行。
|
|
186
|
+
- **工作区内删除优先 `presence_trash`**:移动到 `~/.dsh/presence-trash/<时间戳>/` 并返回恢复提示。
|
|
187
|
+
- **写入是原子的**,保留原文件 mode;`presence_edit` 锚点不唯一时报错而不是猜。
|
|
188
|
+
- **读后写守卫**:内置 `read`/`write`/`edit` 共享读后写守卫;`presence_write` 也会记账,两套 API 可混用。
|
|
189
|
+
|
|
190
|
+
## 配置
|
|
191
|
+
|
|
192
|
+
插件从宿主进程环境变量读取:
|
|
193
|
+
|
|
194
|
+
| 变量 | 默认 | 含义 |
|
|
195
|
+
| --- | --- | --- |
|
|
196
|
+
| `PRESENCE_HOST` | — | 默认 SSH 主机(`~/.ssh/config` 别名或手动主机)。 |
|
|
197
|
+
| `PRESENCE_WORKSPACE` | — | `host:/abs/path`,绑定一个远程工作区。 |
|
|
198
|
+
| `PRESENCE_PASSWORD_<ALIAS>` | — | 某台机器的密码;`<ALIAS>` 大写、非字母数字换成 `_`。 |
|
|
199
|
+
| `PRESENCE_MAX_CHANNELS` | 8 | 每连接并发通道数。 |
|
|
200
|
+
| `PRESENCE_CONNECT_TIMEOUT_MS` | 25000 | 连接尝试超时。 |
|
|
201
|
+
| `PRESENCE_IDLE_TIMEOUT_MS` | 900000 | 空闲连接回收时间(持终端连接不回收)。 |
|
|
202
|
+
| `PRESENCE_READ_ONLY` | false | 拒绝写入/编辑;读取、搜索、执行不受影响。 |
|
|
203
|
+
|
|
204
|
+
完整配置与路径/权限矩阵见 [docs/VERIFICATION.md](docs/VERIFICATION.md) 和
|
|
205
|
+
[docs/HANDOFF.md](docs/HANDOFF.md)。
|
|
206
|
+
|
|
207
|
+
## 开发
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
npm install
|
|
211
|
+
npm install --prefix .dev --ignore-scripts rollup @rollup/plugin-node-resolve \
|
|
212
|
+
@rollup/plugin-commonjs @rollup/plugin-terser codemirror @codemirror/language-data \
|
|
213
|
+
@codemirror/theme-one-dark
|
|
214
|
+
node scripts/build-client.mjs
|
|
215
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.18.0
|
|
216
|
+
# → dist/presence-plugin-0.18.0.zip
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
验证脚本需要一台可 SSH 的主机:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
node scripts/regression.mjs server # 传输层、符号链接、超时、二进制
|
|
223
|
+
node scripts/job-probe.mjs server # 后台任务 + 端口转发
|
|
224
|
+
node scripts/feature-probe.mjs server # wait、会话、pgid、就绪、搜索
|
|
225
|
+
node scripts/import-probe.mjs server # 本地→远端二进制、mode、sha256
|
|
226
|
+
node scripts/safety-probe.mjs server # 危险命令护栏 + 观测状态
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 已知边界
|
|
230
|
+
|
|
231
|
+
- 挂载路径(`~/.dsh/presence/mounts/...`)是虚拟的:对 `ctx.fs` / agent 工具有效,
|
|
232
|
+
但 Explorer、Office、宿主 Python 看不到 —— 需要先用 `presence_export`。
|
|
233
|
+
- sshd 的 `MaxSessions`(默认 10)限制并发通道;连接池最多用 8 个。
|
|
234
|
+
- PTY 终端跟随连接存活;跨重连的 shell 需要目标机有 `tmux`(`presence_terminal { session }` 自动使用)。
|
|
235
|
+
- ProxyJump / ProxyCommand 会被解析并提示,但目前不用于实际拨号。
|
|
236
|
+
- `@` 文件补全按工作区惰性索引远端树(约 10 秒缓存);超大目录首次查询可能要几秒。
|
|
237
|
+
- 端口自动恢复在连接重建时执行;若保存的本地端口已被占用,会回退到其它空闲端口。
|
|
238
|
+
|
|
239
|
+
## 参与贡献
|
|
240
|
+
|
|
241
|
+
欢迎 issue 和 PR。新功能请先开 issue 描述你的远程工作流;修 bug 请保持 diff 聚焦,
|
|
242
|
+
行为变化时在 `scripts/` 下补一个探针。
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
[MIT](LICENSE)
|
|
247
|
+
|
|
248
|
+
## Star History
|
|
249
|
+
|
|
250
|
+
[](https://star-history.com/#fanrj3/dsh-presence&Date)
|