dsh-ssh-manage 0.3.7 → 0.3.9
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.en.md +4 -3
- package/README.md +4 -3
- package/index.js +58 -3
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
`dsh-ssh-manage` is a Web plugin for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness). It turns the hosts in `~/.ssh/config` into manageable objects in the DSH sidebar:
|
|
24
24
|
|
|
25
25
|
- **Host management** — scan the config, groups, tags, favorites, notes, latency and host inspection; SSH aliases are globally unique, so use the repeatable Display name for same names across groups;
|
|
26
|
+
- **DSH Agent integration** — a compact host catalog (alias/group/display name) is injected into the DSH system prompt to keep token use low; full details come from list_hosts on demand, and the Agent uses `ssh_manager` directly for exec, batch commands, inspection and SFTP;
|
|
26
27
|
- **Interactive terminal** — ssh2 allocates a PTY, xterm.js renders it, with colors, arrow keys, full-screen programs such as `vim`/`top`, and window resizing;
|
|
27
28
|
- **Backup and migration** — export/import hosts, groups, tags, notes, favorites and passwords from Settings; exports are re-encrypted with a passphrase (scrypt + AES-256-GCM) and imports merge by alias;
|
|
28
29
|
- **Day-to-day operations** — batch commands, SFTP browse/mkdir/rename/delete/upload/download, connection tests;
|
|
@@ -97,16 +98,16 @@ Creating a host requires alias, hostname and password. The password is not writt
|
|
|
97
98
|
|
|
98
99
|
### From npm (recommended)
|
|
99
100
|
|
|
100
|
-
npm package: [`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage) (current version `0.3.
|
|
101
|
+
npm package: [`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage) (current version `0.3.9`).
|
|
101
102
|
|
|
102
103
|
```text
|
|
103
|
-
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.
|
|
104
|
+
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.9
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
Or with the DSH CLI:
|
|
107
108
|
|
|
108
109
|
```bash
|
|
109
|
-
dsh plugin add dsh-ssh-manage@0.3.
|
|
110
|
+
dsh plugin add dsh-ssh-manage@0.3.9
|
|
110
111
|
```
|
|
111
112
|
|
|
112
113
|
After installation, refresh the Web page; the sidebar shows an "SSH Hosts" entry with id `dsh-ssh-manage`.
|
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
`dsh-ssh-manage` 是 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 的 Web 插件。它把 `~/.ssh/config` 里的主机变成 DSH 侧边栏里可管理、可连接、可运维的对象:
|
|
24
24
|
|
|
25
25
|
- **主机管理**:扫描配置、分组、标签、收藏、备注、连接延迟、主机巡检;SSH 别名全局唯一,不同分组如需同名请使用可重复的“显示名称”;
|
|
26
|
+
- **DSH Agent 集成**:向 DSH 系统提示注入精简主机目录(别名/分组/显示名),地址等详情由 `list_hosts` 按需获取以控制 token;Agent 可直接用 `ssh_manager` 执行命令、批量命令、巡检和 SFTP 操作;
|
|
26
27
|
- **交互式终端**:ssh2 分配 PTY,xterm.js 渲染,支持彩色输出、方向键、`vim`/`top` 等全屏程序和窗口自适应;
|
|
27
28
|
- **备份迁移**:设置页可导出/导入主机、分组、标签、备注、收藏和密码;导出文件用自定义口令二次加密(scrypt + AES-256-GCM),导入按别名合并;
|
|
28
29
|
- **日常运维**:批量命令、SFTP 目录浏览 / 新建 / 重命名 / 删除 / 上传 / 下载、测试连接;
|
|
@@ -99,16 +100,16 @@
|
|
|
99
100
|
|
|
100
101
|
### 从 npm 安装(推荐)
|
|
101
102
|
|
|
102
|
-
npm 包:[`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage)(当前版本 `0.3.
|
|
103
|
+
npm 包:[`dsh-ssh-manage`](https://www.npmjs.com/package/dsh-ssh-manage)(当前版本 `0.3.9`)。
|
|
103
104
|
|
|
104
105
|
```text
|
|
105
|
-
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.
|
|
106
|
+
plugin_manager action=install_bundle target=dsh-ssh-manage@0.3.9
|
|
106
107
|
```
|
|
107
108
|
|
|
108
109
|
或使用 DSH CLI:
|
|
109
110
|
|
|
110
111
|
```bash
|
|
111
|
-
dsh plugin add dsh-ssh-manage@0.3.
|
|
112
|
+
dsh plugin add dsh-ssh-manage@0.3.9
|
|
112
113
|
```
|
|
113
114
|
|
|
114
115
|
安装后刷新 Web 页面,左侧栏会出现「SSH 主机」入口,入口 id 为 `dsh-ssh-manage`。
|
package/index.js
CHANGED
|
@@ -78,6 +78,43 @@ function errorMessage(error) {
|
|
|
78
78
|
return error instanceof Error ? error.message : String(error)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
let sshHostCatalogText = ''
|
|
82
|
+
let sshHostCatalogRefresh = null
|
|
83
|
+
|
|
84
|
+
function sshCatalogLine(host, metadata) {
|
|
85
|
+
const group = String(metadata?.group || '').replace(/[\r\n]+/g, ' ').trim()
|
|
86
|
+
const label = String(metadata?.label || '').replace(/[\r\n"']+/g, ' ').trim()
|
|
87
|
+
let line = `- ${host.host}`
|
|
88
|
+
if (group) line += ` [${group}]`
|
|
89
|
+
if (label && label !== host.host) line += ` "${label.slice(0, 60)}"`
|
|
90
|
+
return line
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Refresh the host catalog injected into the DSH system prompt. */
|
|
94
|
+
export async function refreshSshHostCatalog(paths) {
|
|
95
|
+
if (sshHostCatalogRefresh) return sshHostCatalogRefresh
|
|
96
|
+
sshHostCatalogRefresh = (async () => {
|
|
97
|
+
try {
|
|
98
|
+
const [{ hosts }, metadata] = await Promise.all([
|
|
99
|
+
listSshHosts(paths),
|
|
100
|
+
loadMetadata(paths).catch(() => ({ entries: {} })),
|
|
101
|
+
])
|
|
102
|
+
const lines = hosts.slice(0, 40).map((host) => sshCatalogLine(host, metadata.entries[host.host]))
|
|
103
|
+
if (hosts.length > 40) lines.push(`- ... ${hosts.length - 40} more; call list_hosts`)
|
|
104
|
+
sshHostCatalogText = [
|
|
105
|
+
'SSH hosts available through the dsh-ssh-manage ssh_manager tool. When the user asks to inspect, run commands on, upload to or download from one of these servers, call ssh_manager with the exact alias.',
|
|
106
|
+
'Call action=list_hosts for user/address/port/group/password status. Use exec {host, command}, batch_exec {hosts, command}, inspect_host/test_connection and sftp_* for operations. Passwords are never returned.',
|
|
107
|
+
lines.length ? lines.join('\n') : '- No hosts configured yet; add them in the dsh-ssh-manage sidebar panel.',
|
|
108
|
+
].join('\n').slice(0, 3000)
|
|
109
|
+
} catch {
|
|
110
|
+
sshHostCatalogText = 'SSH host catalog is temporarily unavailable; call ssh_manager action=list_hosts to read it.'
|
|
111
|
+
} finally {
|
|
112
|
+
sshHostCatalogRefresh = null
|
|
113
|
+
}
|
|
114
|
+
})()
|
|
115
|
+
return sshHostCatalogRefresh
|
|
116
|
+
}
|
|
117
|
+
|
|
81
118
|
async function state(paths) {
|
|
82
119
|
const [current, metadataResult, passwordsResult] = await Promise.all([
|
|
83
120
|
listSshHosts(paths),
|
|
@@ -93,6 +130,7 @@ async function state(paths) {
|
|
|
93
130
|
else passwords = passwordsResult.value
|
|
94
131
|
const passwordSet = new Set(passwords)
|
|
95
132
|
const stateDir = storagePaths(paths).stateDir
|
|
133
|
+
void refreshSshHostCatalog(paths)
|
|
96
134
|
return {
|
|
97
135
|
...current,
|
|
98
136
|
stateDir,
|
|
@@ -253,7 +291,7 @@ export async function runAction(paths, action, params = {}) {
|
|
|
253
291
|
function managerTool(paths) {
|
|
254
292
|
return {
|
|
255
293
|
name: 'ssh_manager',
|
|
256
|
-
description: 'Manage SSH hosts, encrypted local passwords, remote commands and SFTP operations. Passwords are never returned.',
|
|
294
|
+
description: 'Manage SSH hosts, encrypted local passwords, remote commands and SFTP operations. Hosts entered in the dsh-ssh-manage panel are listed in the system prompt; when the user asks to operate on a server, call this tool directly with its alias. Use action=exec for one command, batch_exec for several hosts, sftp_* for files. Passwords are never returned.',
|
|
257
295
|
parameters: {
|
|
258
296
|
type: 'object',
|
|
259
297
|
additionalProperties: false,
|
|
@@ -262,7 +300,7 @@ function managerTool(paths) {
|
|
|
262
300
|
action: { type: 'string', enum: ACTIONS, description: 'The operation to run.' },
|
|
263
301
|
host: {
|
|
264
302
|
oneOf: [{ type: 'string' }, { type: 'object' }],
|
|
265
|
-
description: 'Host alias as a string for every action except save_host, which takes a host object: { host, hostname, user, port, identityFile, proxyJump, forwardAgent }.',
|
|
303
|
+
description: 'Host alias as a string (see the SSH host catalog in the system prompt) for every action except save_host, which takes a host object: { host, hostname, user, port, identityFile, proxyJump, forwardAgent }.',
|
|
266
304
|
},
|
|
267
305
|
originalHost: { type: 'string', description: 'Existing alias when renaming a host.' },
|
|
268
306
|
password: { type: 'string', description: 'Password to save for a host. It is write-only.' },
|
|
@@ -830,7 +868,7 @@ function registerAssetRoutes(ctx) {
|
|
|
830
868
|
}
|
|
831
869
|
}
|
|
832
870
|
|
|
833
|
-
export const __test = { resolveSshPaths, listSshHosts, saveSshHost, deleteSshHost, runAction, managerTool, MAX_BODY_BYTES, PTY_ROUTE, PTY_EVENTS_ROUTE, PTY_OPEN_ROUTE, PTY_ATTACH_ROUTE, PTY_INPUT_ROUTE, PTY_RESIZE_ROUTE, PTY_CLOSE_ROUTE, PTY_CLEAR_ROUTE, PTY_TOUCH_ROUTE, PTY_IDLE_MS, sweepIdlePtySessions, MAX_PTY_SESSIONS, PTY_BUFFER_BYTES, ASSETS, handlePtyUpgrade, ptyTextSessions }
|
|
871
|
+
export const __test = { resolveSshPaths, listSshHosts, saveSshHost, deleteSshHost, runAction, managerTool, refreshSshHostCatalog, getSshHostCatalog: () => sshHostCatalogText, MAX_BODY_BYTES, PTY_ROUTE, PTY_EVENTS_ROUTE, PTY_OPEN_ROUTE, PTY_ATTACH_ROUTE, PTY_INPUT_ROUTE, PTY_RESIZE_ROUTE, PTY_CLOSE_ROUTE, PTY_CLEAR_ROUTE, PTY_TOUCH_ROUTE, PTY_IDLE_MS, sweepIdlePtySessions, MAX_PTY_SESSIONS, PTY_BUFFER_BYTES, ASSETS, handlePtyUpgrade, ptyTextSessions }
|
|
834
872
|
|
|
835
873
|
export const inject = ['webServer']
|
|
836
874
|
|
|
@@ -839,7 +877,12 @@ export function apply(ctx) {
|
|
|
839
877
|
throw new Error('dsh-ssh-manage requires DeepSeek Harness >=0.2.1-rc.3: ctx.webServer.register is unavailable.')
|
|
840
878
|
}
|
|
841
879
|
const paths = resolveSshPaths(process.env)
|
|
880
|
+
void refreshSshHostCatalog(paths)
|
|
842
881
|
const runtime = createPtyRuntime()
|
|
882
|
+
const catalogTimer = setInterval(() => {
|
|
883
|
+
if (runtime.active) void refreshSshHostCatalog(paths)
|
|
884
|
+
}, 30_000)
|
|
885
|
+
catalogTimer.unref?.()
|
|
843
886
|
const runtimeIdleTimer = setInterval(() => {
|
|
844
887
|
if (!runtime.active) return
|
|
845
888
|
// Browser heartbeats (/pty/touch) keep attached sessions alive, so a session
|
|
@@ -934,6 +977,7 @@ export function apply(ctx) {
|
|
|
934
977
|
}
|
|
935
978
|
runtime.upgrades.clear()
|
|
936
979
|
clearInterval(runtimeIdleTimer)
|
|
980
|
+
clearInterval(catalogTimer)
|
|
937
981
|
for (const session of ptySessions) {
|
|
938
982
|
try { session.close() } catch {}
|
|
939
983
|
}
|
|
@@ -950,4 +994,15 @@ export function apply(ctx) {
|
|
|
950
994
|
ctx.inject(['tools'], (child) => {
|
|
951
995
|
child.effect(() => child.tools.register(managerTool(paths)), 'dsh-ssh-manage: agent tool')
|
|
952
996
|
})
|
|
997
|
+
ctx.inject(['systemPrompt'], (child) => {
|
|
998
|
+
if (!child.systemPrompt || typeof child.systemPrompt.context !== 'function') return
|
|
999
|
+
child.effect(
|
|
1000
|
+
() => child.systemPrompt.context({
|
|
1001
|
+
name: 'dsh-ssh-manage-hosts',
|
|
1002
|
+
order: 60,
|
|
1003
|
+
text: () => sshHostCatalogText,
|
|
1004
|
+
}),
|
|
1005
|
+
'dsh-ssh-manage: host catalog prompt context',
|
|
1006
|
+
)
|
|
1007
|
+
})
|
|
953
1008
|
}
|