Skip to content
dsh.fish
Bundle

dsh-wsl-launcher

Windows desktop shortcut that boots `dsh web` inside your WSL distribution and opens the browser: one double-click entry point for the DeepSeek Harness Web UI in WSL, plus a wsl_launcher model tool (install / status / open / uninstall).

Source
rqzhao1
stars
1 stars
License
MIT
Updated
Updated 5 days ago

Readme

# dsh-wsl-launcher

**One double-click entry point to the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web UI — for when `dsh` lives inside WSL.**

[中文文档](README.zh.md)

You run `dsh web` inside a WSL distribution, but your mouse is on Windows.
Every time you open the UI you perform the same ritual:

```
open Windows terminal  →  wsl  →  dsh web  →  wait for boot  →  switch to browser  →  type the URL  →  Enter
```

This plugin deletes the ritual. After installing it, your **Windows desktop** hosts an
app-style shortcut (black-whale icon). Double-click it and:

1. `wsl.exe -d <your-distro> --exec /bin/bash <launcher.sh>` boots — no Windows terminal needed;
2. the launcher finds `dsh` (PATH → login shell → recorded install path),
3. if the Web UI port already listens it **just opens the browser** (idempotent — double-click it ten times, nothing breaks),
4. otherwise it starts `dsh web --port 3080` as a detached background daemon, polls the port until ready, then opens your default Windows browser; the console can close without stopping the server.

It also registers a `wsl_launcher` model tool, so any agent in a session can
`install` / `status` / `open` / `uninstall` the launcher itself.

---

## ✅ Compatibility

| | |
|---|---|
| dsh | Verified on `0.1.1-rc.2` (unit tests + scratch-profile composition via `dsh plugin add` / `--dump-config`) |
| Node | `>=20` (as declared in `engines`) |
| Host | WSL1/WSL2 on Windows 10 1607+ / Windows 11 with **interop enabled** (the default) and a Windows drive mounted under `/mnt/<drive>` |
| dsh location | anywhere in the distro: `npm -g`, nvm, local checkout — the launcher resolves it at runtime |
| Last verified | 2026-08-28 (WSL2, Ubuntu 24.04, dsh `0.1.1-rc.2`, Node 24) |

## 📦 Install / Upgrade / Uninstall

Requires a `dsh` profile (e.g. the `web` profile where you run `dsh web`).

**Install**

```sh
# from npm (once published)
dsh plugin --profile web add dsh-wsl-launcher

# or straight from GitHub (monorepo sub-path)
dsh plugin --profile web add github:Small-tailqwq/dsh-deep-whale#path:/wsl-launcher

# or from a local checkout / tarball
dsh plugin --profile web add /path/to/dsh-wsl-launcher
dsh plugin --profile web add /path/to/dsh-wsl-launcher-0.1.0.tgz
```

`dsh plugin` uses the package's `dsh.bundle` manifest to register it as a bundle
layer automatically — **no manual `cordis.patch.yml` edits**.

**Upgrade / Uninstall**

```sh
dsh plugin --profile web update dsh-wsl-launcher
dsh plugin --profile web remove dsh-wsl-launcher
```

Then restart `dsh web`. On activation with `autoInstall: true` (the default) the
plugin (re)creates the launcher files and the desktop shortcut. Uninstalling the
package does not remove your shortcut — use `wsl_launcher` with action
`uninstall` (or the manual steps below) to clean up.

## 🚀 Quick start

1. Install the plugin (see above) and restart `dsh web`.
2. Look at your Windows desktop: **DeepSeek Harness Web** (whale icon).
3. Double-click it → first run boots `dsh web` and opens the browser; later runs
   just open the browser if the server is already up.
4. Reproducible check: ask any agent in a session to call `wsl_launcher` with
   action `status` — expect `isWsl: true`, `distro: "<your distro>"`,
   `running: true` and the URL `http://127.0.0.1:3080`. Use `wsl_launcher` action `stop` to stop only the daemon recorded by this launcher.

> **WSL2 note:** the server listens on `127.0.0.1` inside WSL; WSL's
> localhost forwarding makes it reachable from the Windows browser at the same
> URL — no `--host` flag needed.

## 🤖 wsl_launcher tool

| Action | Effect |
|---|---|
| `install` | (Re)create the launcher script + icon in `~/.dsh/dsh-wsl-launcher` and the whale-icon desktop shortcut (WSL only) |
| `status` | WSL detection, distribution, interop availability, launcher files, and whether the Web UI port is listening (any platform) |
| `stop` | Stop only a live `dsh web` daemon whose PID and command identity match this launcher; never take over another process |
| `open` | Open the Web UI in the default Windows browser (WSL interop) |
| `uninstall` | Remove the desktop shortcut and the whole `~/.dsh/dsh-wsl-launcher` directory (WSL only) | safe to repeat; it only removes launcher-owned files |

## ⚙️ Configuration

Override the plugin row in your profile's `cordis.patch.yml`
(e.g. `~/.dsh/profiles/web/cordis.patch.yml`). A patch replaces the whole row
config, so restate the keys you keep:

```yaml
- id: wsl-launcher
  config:
    autoInstall: true            # create/refresh launcher + shortcut on activation (default true)
    port: 3080                   # Web UI port (default 3080)
    distro: ""                   # WSL distro name; empty = auto (WSL_DISTRO_NAME)
    wslUser: ""                  # WSL user for the shortcut (wsl --user); empty = distro default
    linkName: "DeepSeek Harness Web.lnk"   # shortcut file name on the desktop
    launcherDir: ""              # launcher folder; empty = ~/.dsh/dsh-wsl-launcher
    scriptName: dsh-web-launcher.sh        # launcher file name
    withIcon: true               # copy the whale icon + apply it to the shortcut
  autostopEnabled: false        # opt-in: stop the daemon after all browser tabs close
  autostopGraceMs: 30000       # idle grace period in milliseconds
  autostopMinUptimeMs: 45000   # startup protection in milliseconds
```

### Launch strategy

`launchMode: installed` is the default. The launcher resolves an already installed `dsh` binary (PATH, login shell, common Node manager paths, then the recorded path) and does not contact npm.

For a disposable or `npx`-only setup, opt in explicitly and pin the exact package version:

```yaml
- id: wsl-launcher
  config:
    launchMode: npx
    npxPackage: "@deepseek-ai/dsh@0.1.1-rc.2"
```

This generates `npx --yes @deepseek-ai/dsh@0.1.1-rc.2 web --port 3080 --no-open`. The package field accepts only one exact npm package/version specifier: no omitted version, `latest`, semver range, whitespace, or shell syntax. `npx` mode is never used as an automatic fallback. Node.js and `npx` must be available to the non-interactive WSL launcher; the first run needs npm-registry network access and may download the pinned package.

No secrets are involved.

## 🔐 Permissions & data

| Area | What the plugin does |
|---|---|
| Files written (WSL side) | `~/.dsh/dsh-wsl-launcher/dsh-web-launcher.sh` and `~/.dsh/dsh-wsl-launcher/icons/whale.ico` |
| Files written (Windows side) | One `.lnk` on your desktop (`DeepSeek Harness Web.lnk`) — created by `powershell.exe` (WScript.Shell), no other Windows files touched |
| Files read | Only its own template and icon inside the package, plus `/proc/version` for WSL detection |
| Processes | Spawns `powershell.exe` (shortcut create/remove, browser fallback) and `explorer.exe` / `cmd.exe` (open browser) through WSL interop; the launcher itself is an ASCII bash script you can read before it runs |
| Network | Loopback only: probes `http://127.0.0.1:<port>` for status; the browser is opened by the OS, not by the plugin |
| Credentials | None — never reads, stores, or sends credentials |

## 🔧 How it works

```
┌──────────────────────────────┐   ┌────────────────────────────────────────────┐
│  DeepSeek Harness Web        │──▶│  wsl.exe -d <distro> --exec /bin/bash      │
│  (Windows desktop .lnk,      │   │      ~/.dsh/dsh-wsl-launcher/              │
│  whale .ico via \\wsl.…)     │   │      dsh-web-launcher.sh --port 3080       │
└──────────────────────────────┘   └────────────────────┬───────────────────────┘
                                                        │ (inside WSL)
                        ┌───────────────────────────────┼─────────────────────────┐
                        ▼                               ▼                         ▼
                  where is dsh?                port 3080 listening?      start `dsh web --port 3080`
        (PATH → login shell →          (open Windows browser,          (console = server window;
         recorded install path)         exit 0 — idempotent)            poll /dev/tcp → explorer.exe)
```

- **`cordis.patch.yml`** — the bundle patch: one loader row (`id: wsl-launcher`,
  `name: dsh-wsl-launcher`); `dsh plugin add` reconciles it into the profile's
  bundle stack from the `dsh.bundle` manifest.
- **`lib/launcher.js`** — WSL detection (kernel banner + `WSL_DISTRO_NAME` /
  `WSL_INTEROP` fallbacks), launcher template rendering (pure ASCII),
  `wsl.exe` argument building with command-line quoting, Windows icon
  candidates (`\\wsl.localhost\<distro>\…` first, `\\wsl$\…` fallback), and the
  PowerShell builders for shortcut create/remove. All PowerShell string values
  are single-quoted with `''` escaping.
- **`lib/index.js`** — the Cordis plugin (`name: 'wsl-launcher'`,
  `inject: ['tools']`, zod `Config` schema) plus the `wsl_launcher` tool via
  `@deepseek-ai/dsh-tools` `defineTool`.
- **`lib/template.sh.txt`** — the generated launcher: resolves `dsh` (PATH →
  `bash -ic` login shell (covers nvm) → recorded absolute path), probes the port
  with bash `/dev/tcp` (no curl/netcat), opens the browser via `explorer.exe`,
  falling back to PowerShell `Start-Process` and then `cmd.exe /c start`
  (needed on some WSL setups, e.g. containerized WSL, where `explorer.exe`
  exits without opening a browser), and ties the console window's lifetime to
  the server (`trap` on EXIT/INT/TERM).
- **`icons/whale.ico`** — multi-size whale icon; at install time it is copied
  into the launcher directory so Windows can reach it through the
  `\\wsl.localhost` mapping for the `.lnk` icon.

## 🩺 Troubleshooting

| Symptom | Cause & fix |
|---|---|
| No shortcut after restart | Check `dsh web` logs for a `dsh-wsl-launcher: auto install skipped: …` warning. Usually WSL interop is disabled: set `interop = true` under `[boot]` in `C:\Users\<you>\.wslconfig` (or wsl.conf), restart the distro, then retry `wsl_launcher install`. |
| `powershell.exe not found` in tool output | Same as above — interop disabled or no Windows drive mounted under `/mnt`. |
| Shortcut appears but with a generic icon | The `\\wsl.localhost` mapping may be missing (older Windows). The plugin falls back to `\\wsl$`; if that is gone too the shortcut still works, iconless. Update Windows or set `withIcon: false` to silence the probe. |
| Desktop icon lands in the wrong place | OneDrive-managed desktops: the shortcut targets `%USERPROFILE%\Desktop`, `%OneDrive%\Desktop` or `%USERPROFILE%\OneDrive\Desktop` in that order — the first existing one wins, same as Explorer sees it. |
| Launcher says `dsh was not found` | In the default `installed` mode, `dsh` is not reachable from a non-interactive shell. Install it in the distro (`npm install -g @deepseek-ai/dsh`); if you use nvm, the login-shell probe finds it — or set the recorded path by reinstalling the plugin from the same environment. Alternatively configure explicit, version-pinned `launchMode: npx`. |
| Launcher says `npx was not found` | `launchMode: npx` requires Node.js with `npx` available in the non-interactive WSL environment. Install Node.js/npm or set `NPX_BIN` to an executable `npx` path; keep `npxPackage` an exact pinned version. |
| Launcher says `dsh web exited early` | Read the console output above the error (profile/CLI problem, not a launcher bug). |
| `EADDRINUSE` / port already bound | The launcher detects any listening service and opens the browser instead; it never kills or replaces an unrelated service. Use another `port` in the config if needed. |
| Server stops after the launcher window closes | Current versions use a detached daemon. Use `wsl_launcher stop` or the generated script's `stop` action; an autostop-enabled daemon stops after its browser presence grace period. |
| Autostop endpoints reject requests | The browser client sends same-origin JSON POST requests. Requests with another origin, wrong method, missing JSON content type, malformed JSON, or invalid client ids are rejected. |
| Uninstall says the shortcut is absent | This is an idempotent success state. The Windows shortcut and wrapper are removed when present, and the WSL launcher directory is removed. |
| Browser opens but the page shows a transport/403 error | The **browser-trust fence** rejected a cross-origin call to the local API — open DevTools → Network → the failed request → check its `Origin`; it must be `http://127.0.0.1:<port>`. A browser extension or leftover tab is the usual culprit. |
| No browser window appears | The launcher selects one Windows interop opener: `explorer.exe` when available, otherwise PowerShell `Start-Process` or `cmd.exe /c start`; it never chains launchers after an attempt, preventing duplicate tabs. If none is available it prints the URL. If none works in your WSL setup, check WSL interop; the printed URL is always shown in the console as a last resort. |
| Shortcut boots the wrong distro / user | The `.lnk` encodes `wsl.exe -d <distro> [--user <name>]`. Set `distro` / `wslUser` in the config, then `wsl_launcher install`. |
| Closing the console mid-session | The normal launcher starts a detached daemon, so closing the console does not stop it. Use `wsl_launcher stop`; foreground mode remains available for debugging. |

**Rollback**: `dsh plugin --profile web remove dsh-wsl-launcher`, then
`wsl_launcher` with action `uninstall` (removes the `.lnk` and
`~/.dsh/dsh-wsl-launcher`). Nothing else is left behind.

## 🛠️ Development

- **No build step** — plain ESM JavaScript in `lib/`; zero runtime dependencies
  beyond the dsh peer packages.
- **Tests**: `npm test` (node:test — no third-party dependencies).
- **Manual E2E in a real WSL**: `node tools/e2e-wsl.mjs install|verify|uninstall`
  (dev helper, not published).
- **Release**: bump `version` in `package.json` → `pnpm pack` → publish / push.
  The `dsh.bundle` manifest means `dsh plugin update` picks up new versions.

## ❓ FAQ

**Q: Does the shortcut run anything as admin?** No. `wsl.exe` and the launcher
run with your normal user permissions.

**Q: Does it interfere with a running `dsh web`?** No — the port probe finds a
live server and skips straight to the browser.

**Q: Why a bash script and not a .exe / .cmd?** WSL side is Linux: bash +
`/dev/tcp` needs zero extra tools, stays pure ASCII, and is ~100 lines you can
read before it runs. The Windows side is one standard `.lnk`.

**Q: Non-WSL hosts?** The plugin loads fine, installs nothing, and keeps the
`wsl_launcher` tool active (`status` works everywhere; `open` degrades to a
"open manually" message; `install`/`uninstall` report a clear no-op).

**Q: WSL1?** Works — everything used is interop + files, both present in WSL1.

## 📜 License

MIT — see [LICENSE](LICENSE).

Install

dsh plugin --profile web add github:rqzhao1/dsh-wsl-launcher

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source