dsh-remote 0.5.4 → 0.5.6
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 +11 -3
- package/README.zh.md +11 -3
- package/lib/index.js +29 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
# dsh-remote
|
|
6
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/dsh-remote)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://github.com/topics/dsh-plugin)
|
|
10
|
+
|
|
7
11
|
**Remote-work assistant for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH).**
|
|
8
12
|
|
|
9
13
|
Manage several SSH machines, then pick a **remote workspace** (or a **local** one) and let the agent operate right there without leaving the harness — listing files, reading code, running builds & commands over the remote host, and keeping that remote directory mirrored into a real local workspace object.
|
|
@@ -14,7 +18,7 @@ The harness Web UI intentionally binds `127.0.0.1` (the CLI rejects `--host 0.0.
|
|
|
14
18
|
|
|
15
19
|
Settings → **远程工作区** — a multi-machine SSH registry (add / edit / delete / set-current, password stored locally):
|
|
16
20
|
|
|
17
|
-
<img src="https://
|
|
21
|
+
<img src="https://cdn.jsdelivr.net/gh/flymysql/dsh-remote@main/docs/ui-settings-panel.png" alt="dsh-remote settings — multi-machine registry (light theme, host scrubbed)" width="720"/>
|
|
18
22
|
|
|
19
23
|
The native **"Add workspace" / "Select workspace"** flow — a centered modal, two tabs, opens on **本机 (local)**; switch to **远程 (remote)**:
|
|
20
24
|
|
|
@@ -22,7 +26,7 @@ The native **"Add workspace" / "Select workspace"** flow — a centered modal, t
|
|
|
22
26
|
|
|
23
27
|
Real capture (host scrubbed to a placeholder):
|
|
24
28
|
|
|
25
|
-
<img src="https://
|
|
29
|
+
<img src="https://cdn.jsdelivr.net/gh/flymysql/dsh-remote@main/docs/ui-picker-panel.png" alt="dsh-remote workspace picker — real dialog; 本机 (local) tab; 远程 machine select + prefilled root path + autocomplete" width="720"/>
|
|
26
30
|
|
|
27
31
|
---
|
|
28
32
|
|
|
@@ -122,4 +126,8 @@ Giving the plugin a machine's credentials lets the agent run **shell commands as
|
|
|
122
126
|
|
|
123
127
|
## License
|
|
124
128
|
|
|
125
|
-
MIT
|
|
129
|
+
MIT
|
|
130
|
+
|
|
131
|
+
## Changelog
|
|
132
|
+
|
|
133
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
package/README.zh.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
# dsh-remote
|
|
6
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/dsh-remote)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://github.com/topics/dsh-plugin)
|
|
10
|
+
|
|
7
11
|
**为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)打造的远程工作助手。**
|
|
8
12
|
|
|
9
13
|
维护多台 SSH 机器,然后在「选择工作区」时选一个**远程工作区**(或**本地工作区**),Agent 就能在不离开 harness 的情况下直接操作——列文件、读代码、在远程主机上跑构建/命令,并把远程目录镜像成一个真实的本地工作区对象。
|
|
@@ -14,7 +18,7 @@ DSH 的 Web 界面刻意只监听 `127.0.0.1`(CLI 为安全拒绝 `--host 0.0.
|
|
|
14
18
|
|
|
15
19
|
设置 → **远程工作区** —— 多机 SSH 列表(增/删/改/设为当前,密码本地保存、不回显):
|
|
16
20
|
|
|
17
|
-
<img src="https://
|
|
21
|
+
<img src="https://cdn.jsdelivr.net/gh/flymysql/dsh-remote@main/docs/ui-settings-panel.png" alt="dsh-remote 设置页 — 多机列表(浅色主题,主机已打码)" width="720"/>
|
|
18
22
|
|
|
19
23
|
原生 **「Add workspace / 选择工作区」** 流程 —— **居中弹窗**、两个 tab,**默认落在「本机」**;切到 **「远程」**:
|
|
20
24
|
|
|
@@ -22,7 +26,7 @@ DSH 的 Web 界面刻意只监听 `127.0.0.1`(CLI 为安全拒绝 `--host 0.0.
|
|
|
22
26
|
|
|
23
27
|
真实截图(机器已打码为占位):
|
|
24
28
|
|
|
25
|
-
<img src="https://
|
|
29
|
+
<img src="https://cdn.jsdelivr.net/gh/flymysql/dsh-remote@main/docs/ui-picker-panel.png" alt="dsh-remote 工作区选择 — 真实弹窗;默认本机 tab;远程:机器下拉 + 预填根路径 + 自动补全" width="720"/>
|
|
26
30
|
|
|
27
31
|
---
|
|
28
32
|
|
|
@@ -123,4 +127,8 @@ npx --yes @deepseek-ai/dsh plugin --profile web remove dsh-remote # 恢复用
|
|
|
123
127
|
|
|
124
128
|
## License
|
|
125
129
|
|
|
126
|
-
MIT
|
|
130
|
+
MIT
|
|
131
|
+
|
|
132
|
+
## 变更记录
|
|
133
|
+
|
|
134
|
+
见 [CHANGELOG.md](./CHANGELOG.md)。
|
package/lib/index.js
CHANGED
|
@@ -41,7 +41,7 @@ export const Config = z.object({
|
|
|
41
41
|
username: z.string().default(''),
|
|
42
42
|
/** Password login (only when the remote has no key. Override the fallback below). */
|
|
43
43
|
password: z.string().default(''),
|
|
44
|
-
/**
|
|
44
|
+
/** Explicit SSH private-key path (optional; only used when supplied). Never auto-reads ~/.ssh. */
|
|
45
45
|
privateKeyPath: z.string().default(''),
|
|
46
46
|
/** Key passphrase when the key is encrypted. */
|
|
47
47
|
passphrase: z.string().default(''),
|
|
@@ -236,7 +236,7 @@ class SshPool {
|
|
|
236
236
|
|
|
237
237
|
resolveKeyPath() {
|
|
238
238
|
const p = this.config.privateKeyPath
|
|
239
|
-
if (!p) return
|
|
239
|
+
if (!p) return ''
|
|
240
240
|
if (p.startsWith('~/') || p === '~') return path.join(homedir(), p.slice(1))
|
|
241
241
|
return p
|
|
242
242
|
}
|
|
@@ -296,13 +296,15 @@ class SshPool {
|
|
|
296
296
|
if (this.config.password) {
|
|
297
297
|
opts.password = this.config.password
|
|
298
298
|
} else {
|
|
299
|
+
const keyPath = this.resolveKeyPath()
|
|
300
|
+
if (!keyPath) {
|
|
301
|
+
return fail(new Error('no credentials: set a password or a privateKeyPath to connect'))
|
|
302
|
+
}
|
|
299
303
|
let key
|
|
300
304
|
try {
|
|
301
|
-
key = readFileSync(
|
|
305
|
+
key = readFileSync(keyPath)
|
|
302
306
|
} catch (err) {
|
|
303
|
-
return fail(
|
|
304
|
-
new Error(`cannot read private key "${this.resolveKeyPath()}": ${err && err.message}`),
|
|
305
|
-
)
|
|
307
|
+
return fail(new Error(`cannot read private key "${keyPath}": ${err && err.message}`))
|
|
306
308
|
}
|
|
307
309
|
opts.privateKey = key
|
|
308
310
|
opts.passphrase = this.config.passphrase || undefined
|
|
@@ -496,7 +498,7 @@ export async function apply(ctx, config) {
|
|
|
496
498
|
'Show the remote environment: host/user/port, connection health, and the current remote workspace path. Call this first to orient, or when a remote_* call fails to check connectivity.',
|
|
497
499
|
parameters: {},
|
|
498
500
|
output: {
|
|
499
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
501
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
500
502
|
render: (_a, v) => [{ type: 'text', text: v.text }],
|
|
501
503
|
},
|
|
502
504
|
async execute() {
|
|
@@ -528,14 +530,15 @@ export async function apply(ctx, config) {
|
|
|
528
530
|
description:
|
|
529
531
|
'Connect SSH to a remote host for remote workspace work. Provide host (required), user, optional password or privateKeyPath/port. Once connected, call rw_pick_workspace to pick the workspace directory this session should work in.',
|
|
530
532
|
parameters: {
|
|
531
|
-
|
|
533
|
+
required: ['host'],
|
|
534
|
+
host: { type: 'string', description: 'Remote host IP or hostname' },
|
|
532
535
|
username: { type: 'string', description: 'SSH user (default from config or root)' },
|
|
533
536
|
port: { type: 'integer', description: 'SSH port (default 22)' },
|
|
534
537
|
password: { type: 'string', description: 'SSH password (prefer SSH key when possible)' },
|
|
535
538
|
privateKeyPath: { type: 'string', description: 'Absolute private-key path' },
|
|
536
539
|
},
|
|
537
540
|
output: {
|
|
538
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
541
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
539
542
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
540
543
|
},
|
|
541
544
|
async execute(args) {
|
|
@@ -563,10 +566,11 @@ export async function apply(ctx, config) {
|
|
|
563
566
|
description:
|
|
564
567
|
'Set the remote workspace directory this session should treat as its working root on the connected remote. Verifies it exists (a directory). Use rw_list_dir to browse first if unsure.',
|
|
565
568
|
parameters: {
|
|
566
|
-
|
|
569
|
+
required: ['path'],
|
|
570
|
+
path: { type: 'string', description: 'Absolute remote directory path, e.g. /home/dev/code/project' },
|
|
567
571
|
},
|
|
568
572
|
output: {
|
|
569
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
573
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
570
574
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
571
575
|
},
|
|
572
576
|
async execute(args) {
|
|
@@ -592,7 +596,7 @@ export async function apply(ctx, config) {
|
|
|
592
596
|
maxFiles: { type: 'integer', description: 'Max files to download (default 500)' },
|
|
593
597
|
},
|
|
594
598
|
output: {
|
|
595
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
599
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
596
600
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
597
601
|
},
|
|
598
602
|
async execute(args) {
|
|
@@ -621,7 +625,7 @@ export async function apply(ctx, config) {
|
|
|
621
625
|
maxFiles: { type: 'integer', description: 'Max files to upload (default 500)' },
|
|
622
626
|
},
|
|
623
627
|
output: {
|
|
624
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
628
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
625
629
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
626
630
|
},
|
|
627
631
|
async execute(args) {
|
|
@@ -649,7 +653,7 @@ export async function apply(ctx, config) {
|
|
|
649
653
|
path: { type: 'string', description: 'Absolute remote path (default: current remote workspace)' },
|
|
650
654
|
},
|
|
651
655
|
output: {
|
|
652
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
656
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
653
657
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
654
658
|
},
|
|
655
659
|
async execute(args) {
|
|
@@ -664,13 +668,14 @@ export async function apply(ctx, config) {
|
|
|
664
668
|
description:
|
|
665
669
|
'Read a text file on the remote host with line numbers. Supports paging with startLine/endLine. Path is absolute.',
|
|
666
670
|
parameters: {
|
|
667
|
-
|
|
671
|
+
required: ['path'],
|
|
672
|
+
path: { type: 'string', description: 'Absolute remote file path' },
|
|
668
673
|
startLine: { type: 'integer', description: '1-based first line (default 1)' },
|
|
669
674
|
endLine: { type: 'integer', description: '1-based last line (inclusive)' },
|
|
670
675
|
maxLines: { type: 'integer', description: 'Max lines (default 2000)' },
|
|
671
676
|
},
|
|
672
677
|
output: {
|
|
673
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
678
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
674
679
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
675
680
|
},
|
|
676
681
|
async execute(args) {
|
|
@@ -693,10 +698,11 @@ export async function apply(ctx, config) {
|
|
|
693
698
|
description:
|
|
694
699
|
'Run a shell command on the remote host. Use for anything that is not reading a file (build, test, grep, etc). Output is capped.',
|
|
695
700
|
parameters: {
|
|
696
|
-
|
|
701
|
+
required: ['command'],
|
|
702
|
+
command: { type: 'string', description: 'Shell command (run on the remote host)' },
|
|
697
703
|
},
|
|
698
704
|
output: {
|
|
699
|
-
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string'
|
|
705
|
+
schema: { type: 'object', additionalProperties: false, required: ['text'], properties: { text: { type: 'string' } } },
|
|
700
706
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
701
707
|
},
|
|
702
708
|
async execute(args) {
|
|
@@ -711,12 +717,13 @@ export async function apply(ctx, config) {
|
|
|
711
717
|
description:
|
|
712
718
|
'Write text to a file on the remote host (creating parent directories if needed). Path is absolute. Use this to create or overwrite a remote file directly, instead of round-tripping through a local mirror.',
|
|
713
719
|
parameters: {
|
|
714
|
-
|
|
715
|
-
|
|
720
|
+
required: ['path', 'content'],
|
|
721
|
+
path: { type: 'string', description: 'Absolute remote file path' },
|
|
722
|
+
content: { type: 'string', description: 'File content to write (overwrites existing file)' },
|
|
716
723
|
mkdir: { type: 'boolean', description: 'Create missing parent directories (default true)' },
|
|
717
724
|
},
|
|
718
725
|
output: {
|
|
719
|
-
schema: { type: 'object', additionalProperties: false, properties: { ok: { type: 'boolean'
|
|
726
|
+
schema: { type: 'object', additionalProperties: false, required: ['ok'], properties: { ok: { type: 'boolean' }, bytes: { type: 'integer' }, text: { type: 'string' } } },
|
|
720
727
|
render: (_a, a) => [{ type: 'text', text: a.text || (a.ok ? 'written' : 'failed') }],
|
|
721
728
|
},
|
|
722
729
|
async execute(args) {
|
|
@@ -754,7 +761,7 @@ export async function apply(ctx, config) {
|
|
|
754
761
|
'Close the current SSH connection to the remote host, releasing the persistent pool. Useful to rotate connections or after a long idle.',
|
|
755
762
|
parameters: {},
|
|
756
763
|
output: {
|
|
757
|
-
schema: { type: 'object', additionalProperties: false, properties: { ok: { type: 'boolean'
|
|
764
|
+
schema: { type: 'object', additionalProperties: false, required: ['ok'], properties: { ok: { type: 'boolean' }, text: { type: 'string' } } },
|
|
758
765
|
render: (_a, a) => [{ type: 'text', text: a.text }],
|
|
759
766
|
},
|
|
760
767
|
async execute() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Remote-work assistant for DeepSeek Harness: connect SSH (password or key), pick a remote workspace, operate on it with rw_pick_workspace / rw_list_dir / rw_read_file / rw_exec / rw_sync tools, and mirror it to a real local directory (SFTP) so the DSH native workspace can adopt it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "flymysql <flyphp@outlook.com>",
|