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 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.7`).
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.7
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.7
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.7`)。
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.7
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.7
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ssh-manage",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "A DSH sidebar plugin for managing SSH host profiles with a real interactive PTY terminal.",
5
5
  "license": "MIT",
6
6
  "private": false,