Introduction

When driving dsh using scripts, desktop shells, or editor plugins, a common problem arises: capabilities such as changing language, switching workspaces, and sensing when the agent is idle are all embedded within the dsh process. External processes either directly reach in to call its in-process services, or write their own channels, resulting in inconsistent interfaces and heavy coupling.

dsh-api’s approach is to converge this into a single entry point: dsh already listens on HTTP at 127.0.0.1, so plugins can directly mount a set of /dsh-api/* JSON routes on this socket. Any process on the same machine—desktop wrappers, browser extensions, CLI, editor integrations—goes through here. Below, I introduce its positioning, interface, installation, and common issues.

What is it

dsh-api is a dsh plugin maintained by lilming123, current version 0.1.0, MIT license. It exposes dsh’s internal capabilities—language settings, workspace registry, companion bridging—as JSON routes on the port dsh is already listening on at 127.0.0.1. It does not open new ports, has zero runtime dependencies other than Node itself, and the route prefix defaults to /dsh-api.

The DSH ecosystem philosophy is “everything is a plugin,” and this plugin follows this premise: install into the profile, and load automatically with dsh web.

Core Feature: Two-Layer Routing

Routes are divided into two layers, mounted under the same /dsh-api prefix.

Native Routes

Available immediately after the plugin loads, without depending on any extra process:

Method Path Purpose
GET /dsh-api/health Liveness check and basic identity info (dsh port, cwd, companion registration status)
GET /dsh-api/language Read locale.preference
POST /dsh-api/language Write locale.preference (zh or en)
GET /dsh-api/workspace/list List all workspaces from workspaceRegistry
GET /dsh-api/workspace/current Current cwd and companion snapshot
POST /dsh-api/workspace/create Register a new workspace with { path, title? }
GET /dsh-api/events SSE event stream

Event Stream

GET /dsh-api/events is a SSE long connection. There are five types of events:

  • ready: Connection established;
  • agent-idle: Triggered when any agent/status event transitions from running to idle, carrying fields like sessionId, title, previousStatus, etc.;
  • approval-needed: A read-only bypass of the dsh approval/request waterfall—the plugin observes the request, broadcasts a summary, and hands control back to the real response chain;
  • heartbeat: Every 25 seconds, to prevent disconnection due to agent idleness;
  • server-stopping: Broadcasted before dsh shuts down, after which the socket closes.

Companion Bridging Routes

The second layer requires a registered companion. Companion refers to any local process: write { port, token, pid, ... } to $DSH_HOME/dsh-api-companion.json and implement the /companion/* protocol. When no companion is registered, these routes return 503, while native routes are unaffected:

Method Path Purpose
GET /dsh-api/companion/state Companion state snapshot
POST /dsh-api/workspace/open Switch dsh cwd (restarts dsh under companion)
POST /dsh-api/input/paste Inject text into the dsh UI
POST /dsh-api/window/show Focus the host window
POST /dsh-api/window/reload Reload the host window
POST /dsh-api/app/quit Quit the host application

Installation and Enablement

First install the plugin, then confirm the loading method:

dsh plugin --profile web add github:lilming123/dsh-api

The README also lists the npm form (dsh plugin --profile web add dsh-api), but notes it as “once published,” so current availability depends on the repository README.

Behavior after installation:

  1. dsh plugin is a pnpm thin wrapper; the package falls into $DSH_HOME/profiles/<profile>/node_modules/;
  2. dsh-api is registered into the bundle list of that profile;
  3. The next time dsh web loads automatically, no --patch is needed.

Node version requirement >= 20. Additionally, the desktop client dsh-desktop will skip its built-in fallback version if it detects an installed dsh-api; you only need to manually install this plugin when directly driving dsh without dsh-desktop.

Configuration

The plugin has two configuration items, written in the loader entry’s config:

Key Default Value Purpose
basePath /dsh-api HTTP route prefix
companionFile $DSH_HOME/dsh-api-companion.json Companion discovery file, read on demand

For example, to change the route prefix to /control, write in $DSH_HOME/profiles/web/cordis.patch.yml:

- id: dsh-api
  config:
    basePath: /control

This way, all routes start with /control.

Typical Usage

Following the steps above, the plugin is loaded with dsh web. Below, I walk through common interfaces using curl (the port continues to be 3181 as in the README example; adjust according to your actual startup parameters).

  1. Liveness check:
curl http://127.0.0.1:3181/dsh-api/health

Returns dsh port, cwd, companion registration status, etc., useful for confirming the plugin is loaded.

  1. Subscribe to event stream:
curl -N http://127.0.0.1:3181/dsh-api/events

-N tells curl not to buffer, directly printing SSE frames: ready, agent-idle, approval-needed, and the heartbeat every 25 seconds.

  1. Write language preference and register workspace: POST /dsh-api/language accepts { "language": "zh" | "en" }; POST /dsh-api/workspace/create accepts { path, title? }, where path points to the directory to register and title is optional.

  2. Run example scripts: There are two directly runnable files under the repository examples/, curl.sh iterates through all endpoints, and events.mjs is a zero-dependency Node SSE subscription example suitable as a starting point for your own integration.

  3. Debugging with development mode:

git clone https://github.com/lilming123/dsh-api.git
cd dsh-api

mkdir -p "$DSH_HOME/profiles/web/dsh-api-dev"
ln -sf "$PWD/index.mjs" "$DSH_HOME/profiles/web/dsh-api-dev/index.mjs"
cat > /tmp/dsh-api-dev.patch.yml <<'YML'
- insert:
    - id: dsh-api-dev
      name: ./dsh-api-dev/index.mjs
YML

dsh web --patch /tmp/dsh-api-dev.patch.yml --port 3181

dsh-api is a pure ESM plugin with no build steps. The approach is to clone index.mjs and symlink it into the profile’s dsh-api-dev/ directory, then use --patch to insert it into the loading list and specify port 3181 to start.

Security Model

Three types of constraints are worth knowing:

  • Binds only to 127.0.0.1 which dsh is already listening on; does not open new ports, so the internet cannot access it;
  • Change requests validate the Origin: Origins without an Origin header (CLI scenario) or loopback origins are allowed; others return 403;
  • Companion bridging routes forward the token from the discovery file via the x-dsh-api-companion-token request header, which is validated on the companion side; mismatches result in rejection.

FAQ

Check by symptom:

  • All /dsh-api/* return 404: Plugin not loaded. Check if ~/.dsh/profiles/web/package.json’s dsh.profile.bundles contains dsh-api and if ~/.dsh/profiles/web/node_modules/dsh-api exists; run the installation command again if either is missing;
  • POST /dsh-api/workspace/create returns 503: Current dsh context is missing workspaceRegistry—either the dsh version is older than this service, or the profile patch removed it. Upgrade dsh (npm i -g @deepseek-ai/dsh) and try again;
  • Companion routes return 503: No companion registered. This is expected behavior when running dsh directly without a wrapper like dsh-desktop; native routes (/health, /language, /workspace/*, /events) remain usable;
  • SSE stream disconnects every ~60 seconds: Reverse proxy kills the connection while idle. The plugin sends a heartbeat every 25 seconds; if it still disconnects, increase the proxy idle timeout or remove the proxy—dsh-api binds to 127.0.0.1 and doesn’t strictly need a proxy.

Applicable Scenarios and Notes

Who is it for:

  • Tool authors building desktop shells, browser extensions, or editor integrations for dsh, who need a unified HTTP entry point and don’t want to figure out in-process services on their own;
  • Scripts that need to listen for agent idle events or approval events to perform external automation;
  • Scenarios where you need to read or register workspaces from the outside, or switch dsh cwd.

Note: The plugin runs with the permissions of the current dsh process; through the exposed interfaces, it can access resources the dsh process can access. It is recommended to read the source code before installation (the main part is index.mjs, pure ESM with no build steps) and confirm that the MIT license fits your usage.

Summary

The value of dsh-api lies in convergence: reusing the existing 127.0.0.1 socket, a fixed set of /dsh-api/* JSON routes, plus an optional layer of companion bridging, providing a unified entry point for external tools to drive dsh. See the GitHub repository: https://github.com/lilming123/dsh-api; Community directory page (independent site, no official affiliation with DeepSeek or Huafang): https://www.skillhub.cn/plugins/lilming123/dsh-api.