Bundle
dsh-remote-workspaces
Open folders on remote hosts over SSH as DeepSeek Harness workspaces — a non-invasive DSH bundle.
- Source
- januory
- stars
- 1 stars
- License
- MIT
- Updated
- Updated yesterday
Readme
# dsh-remote-workspaces
English | [中文](README.zh.md)
Open folders on remote hosts over SSH as first-class [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) workspaces.
## What it does
Once a remote directory is opened as a workspace, the agent's file tools (`read` / `write` / `edit` / `grep` / `glob`) and shell commands land **directly on the remote host** over SSH, not a local mirror. There is no mirroring and no sync: every operation is executed remotely in real time, and local workspaces keep their full sandbox behavior.
## Features
- **Open a remote folder as a workspace** — browse the remote filesystem, pick a directory, and the harness adopts it as if it were local. All I/O routes to the remote.
- **Multi-machine registry** — add, edit, and remove SSH hosts (alias, host, port, user, key or password). Credentials are AES-256-GCM encrypted at rest and never sent back to the browser.
- **`~/.ssh/config` import** — list configured aliases and pre-fill the connection form.
- **Connection test** — verify a host before using it.
- **Transparent tool routing** — `read`/`write`/`edit` go through SFTP, `grep`/`glob` run ripgrep on the remote, and `bash`/`pwsh` commands execute over ssh2 exec. Local paths keep the harness's own sandbox.
- **Policy-aware remote mutations** — the harness file policy applies to remote workspaces too: `read-only` denies `write`/`edit` and remote shell commands, `workspace-write` confines `write`/`edit` to the remote workspace root (plus `/tmp`) while remote shell commands need a `danger-full-access` approval, and `danger-full-access` delegates to the SSH account.
## How it works
The bundle has a host half and a browser half:
- **Host** (`src/index.js`) — an SSH/SFTP transport built on [`ssh2`](https://github.com/mscdex/ssh2), an encrypted machine store, the routing filesystem (`ctx.fs`) and shell (`ctx.shell`) providers, remote-aware `grep`/`glob` tools, and a `remoteWorkspaces` Remote namespace exposed through Typert.
- **Browser** (`src/client.js`) — a "Remote Workspaces" settings section (hosts grouped by machine) and a workspace-add picker.
Opening a remote directory creates an **anchor**: a real but empty local directory adopted by the harness as the workspace identity, plus a metadata file recording its remote origin. A registry (`anchors.json`) maps that anchor to its remote host and path, and every file/command operation is routed to the remote by that lookup:
```
$DSH_HOME/remote-workspaces/
├── machines.json # host registry (credentials encrypted at rest)
├── anchors.json # anchor → remote origin routing registry
└── <host>-<user>-<port>/ # per-host anchors
└── <encoded-path>/ # one empty anchor per remote path
│ └── .dsh-remote-meta.json
```
The full remote path is encoded into the anchor name (path separators and Windows-illegal characters become `--`), so `/home/test` and `/data/test` map to distinct directories (`home--test` vs `data--test`).
## Requirements
- A DeepSeek Harness installation (the plugin resolves its host services at runtime).
- Node.js ≥ 18 (the harness itself runs on Node 22+).
- `ssh2` is the only transport dependency; no external `ssh` binary is needed.
- The remote host needs `rg` (ripgrep) for `grep`/`glob`, and `sha256sum` (or `shasum`) for post-write verification — both degrade gracefully when absent.
## Installation
Install from this repository:
```sh
dsh plugin --profile web add github:januory/dsh-remote-workspaces
```
Once published to npm, install the released package directly:
```sh
dsh plugin --profile web add dsh-remote-workspaces
```
Install from source:
```sh
git clone https://github.com/januory/dsh-remote-workspaces.git
cd dsh-remote-workspaces
pnpm install # install the ssh2 transport dependency
dsh plugin --profile web add .
```
Remove it with:
```sh
dsh plugin --profile web remove dsh-remote-workspaces
```
> Maintainer note: the npm package name is now `dsh-remote-workspaces` (the early git install used `remote-workspaces`); older installs must first run `dsh plugin --profile <name> remove remote-workspaces`, then install under the new name.
## Usage
1. Open **Settings → Remote Workspaces**.
2. Add an SSH host (or import one from `~/.ssh/config`), then **Test connection**.
3. In the workspace-add flow, choose a remote host, browse to a directory, and open it. The harness adopts the empty anchor as a workspace, and reads, writes, searches, and shell commands all execute on the remote.
## Remote API
The host exposes a `remoteWorkspaces` Remote namespace (Typert) with these invocations: `listMachines`, `saveMachine`, `deleteMachine`, `listSshAliases`, `sshAliasDetail`, `testConnection`, `listRemoteDir`, and `openRemoteWorkspace`.
## Repository structure
```
src/ # the DSH bundle source
index.js # host entry (routing fs/shell, search tools, Remote namespace)
client.js # browser entry (settings UI + picker)
transport.js # ssh2 transport (SshClient, exec, sha256)
routing-fs.js # routing filesystem (remote SFTP / local fence)
fs-sftp.js # SFTP filesystem backend
local-backend.js # local filesystem backend
containment.js # local sandbox containment fence
shell-exec.js # SshShellExecutor (remote ssh2 exec / local subprocess)
search.js # remote-aware grep/glob tools
anchor.js # local anchor layout (empty dir + meta)
registry.js # anchors.json routing registry
machine-store.js # host registry (encrypted at rest)
ssh-config.js # ~/.ssh/config parser
ssh-uri.js # ssh:// URI parsing + detection
errors.js # error codes
cordis.patch.yml # DSH bundle patch (swap in the routing providers)
package.json # package + dsh manifest
test/ # unit + integration tests
```
## Testing
Pure unit tests (no host required) run anywhere:
```sh
pnpm test
```
The integration suite exercises a real SSH host and expects the machine registry to be configured first (it targets the first machine in the registry, or the one aliased `test`):
```sh
pnpm test:integration
```
## Publishing (maintainers)
Releases are fully manual with **zero input**: the version comes straight from `package.json` — commit the new `version` on `main` first, then GitHub Actions → **release** → **Run workflow**. The workflow runs the unit tests (`npm test`), guards that the `v<version>` tag does not exist yet (prevents duplicate releases), publishes to npm with provenance (`npm publish --provenance`), creates and pushes the `v<version>` tag, and creates a GitHub Release with `--generate-notes`.
Prerequisites: configure an `NPM_TOKEN` secret in **Settings → Secrets and variables → Actions** (an npm automation token, or a granular token with publish permission on the package); the publishing account must be the npm user owning the package name.
Equivalent manual flow:
```sh
npm login
npm publish --provenance
git tag "v$(node -p \"require('./package.json').version\")"
git push origin --tags
```
## License
[MIT](./LICENSE)
Install
dsh plugin --profile web add github:januory/dsh-remote-workspaces
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-remote-workspaces from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.