YOLO-LZL
dsh-ssh-plugin
No description
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 14, 2026
- Updated
- Aug 14, 2026
Introduction
dsh-ssh-plugin
A DeepSeek Harness plugin that adds the ssh_exec model tool — run commands on a remote Linux server over the system ssh client — plus persisted SSH connections: save connections once, bind them to a workspace, and let ssh_exec use them by default.
- Key-based auth only (
BatchMode=yes) — no password/passphrase prompts, so a missing or unauthorized key fails fast instead of hanging. - No remote-shell quoting layer — the remote command travels through ssh stdin to
bash -s. - Session sandbox aware — runs the client under the calling session's standing policy;
danger-full-accessruns it unconfined. - Recognizes your local
~/.ssh/config— hosts defined there need no manualssh_connections save; pass the alias asssh_exechostand ssh resolves it, andssh_connections listshows them read-only. - Dual-face package — the host half registers the
ssh-connectionssettings namespace and thessh_connectionstool; the browser half registers the "SSH Connections" card in Settings → Plugins. - All harness services except
tools/shellare optional — profiles withoutsettingsorworkspaceRegistrydegrade gracefully (see below).
Install
dsh plugin --profile <name> add dsh-ssh-plugin # from npm
dsh plugin --profile <name> add github:you/dsh-ssh-plugin # from git
dsh plugin --profile <name> add ./dsh-ssh-plugin # from a local checkout
A local checkout must be built first: pnpm install && pnpm bundle (git installs build automatically through prepare).
Then boot with dsh --profile <name>.
The plugin declares the harness packages as peer dependencies; @deepseek-ai/dsh-base (the first bundle of every profile) provides them.
Usage
ssh_exec
The model gains an ssh_exec tool. Ask in natural language (e.g. "ssh to root@10.0.0.5 and show disk usage"), or pass explicit arguments:
| Arg | Required | Notes |
|---|---|---|
host | yes* | Remote host as [user@]host, e.g. root@10.0.0.5, or any alias defined in ~/.ssh/config. |
command | yes | Bash command(s) run on the remote host via bash -s. |
port | no | SSH port. Default 22. |
key_path | no | Absolute path to the private key. Omit for ~/.ssh defaults and ssh-agent. |
timeout_ms | no | Foreground timeout in ms. Default 30000. |
connection | no | Label of a saved connection; fills in host/port/user/keyPath. |
* host is required in the schema, but at execution the effective host resolves as explicit host → explicit connection → workspace default. Explicit port/key_path always win over the resolved connection's values. A host that is a ~/.ssh/config alias is handed to ssh unchanged — ssh applies the config's HostName/User/Port/IdentityFile itself, so no ssh_connections save is needed for config hosts.
ssh_connections
The model also gains an ssh_connections tool for durable connection management (persisted through the harness settings store, so it survives restarts):
save— persist a connection (label,host, optionalport/user/keyPath). Labels are unique.list— show saved connections plus hosts discovered from the local~/.ssh/config(read-only; pass the alias asssh_exechost, no save needed).delete— remove one by label (also unbinds workspaces that pointed at it).use— bind the current workspace to a connection label sossh_execmay omithost; omitlabelto clear the workspace default.
SSH config discovery
On every call the plugin reads the local OpenSSH client config (~/.ssh/config, or sshConfigPath when configured) and parses it:
- Concrete
Hostaliases become visible to the model inssh_connections listand are suggested inssh_execerror messages. - Per ssh semantics, the first obtained value wins: global directives and
Host *blocks provide defaults, later matching blocks cannot override them.HostName/User/Port/IdentityFileare resolved;Matchblocks andIncludedirectives are not expanded — hosts must live in the file itself. - Discovery is best-effort: a missing/unreadable config yields an empty list, never an error. Config hosts are never persisted — they exist only in your
~/.ssh/config.
Settings card
With a web profile, Settings → Plugins shows an "SSH Connections" card styled like the built-in plugin cards (Bash/Terminal, Agent loop, Web search): a collapsible card with an unsaved badge and discard/save actions. Edit the same connections list (add/remove rows, save). The card only writes the connections field; workspace bindings are managed by ssh_connections use.
Data model
The ssh-connections settings namespace (schemastery schema, validated for duplicate labels):
{
connections: [{
label: string // unique, non-empty
host: string // [user@]host, non-empty
port?: number // 1..65535, default 22
user?: string // optional; prepended to host when host has no user part
keyPath?: string // absolute private-key path; omit for ~/.ssh + ssh-agent
}],
workspaceDefaults: { [workspaceId: string]: string } // workspaceId → label
}
Only key paths are stored — key content never leaves disk (role('secret') is reserved for a future keyContent field).
Configuration
Set row-level defaults in a later patch layer (profile cordis.patch.yml or --patch):
- id: tool-ssh
name: dsh-ssh-plugin
config:
timeoutMs: 30000
connectTimeout: 15
sshConfigPath: C:\Users\me\.ssh\config # optional; defaults to ~/.ssh/config
Degradation matrix
| Composition | Behavior |
|---|---|
No ctx.settings (e.g. TUI) | No namespace, no ssh_connections tool; ssh_exec works with explicit args only. |
No ctx.workspaceRegistry | use and the workspace-default resolution report "workspace registry unavailable"; everything else works. |
No ~/.ssh/config (or unreadable) | ssh_connections list shows saved connections only; ssh_exec suggestions omit config aliases. |
| Namespace not exposed to the browser | The card renders an "unavailable" placeholder. |
Requirements
- A reachable Linux target with bash and key-based SSH access set up (
~/.ssh/authorized_keys). - On Windows, a session whose standing sandbox policy is
danger-full-access; the restricted token otherwise refuses to startssh.exe.
Development
pnpm install
pnpm bundle # tsdown → lib/index.js (host) + lib/client.js (browser bundle)
pnpm types # tsc --emitDeclarationOnly → lib/types/**/*.d.ts
pnpm test # vitest (model + wiring)