dsh-prompt-optimizer
winditer/dsh-prompt-optimizer
Polishes the draft in the composer into a clearer, better-structured prompt from the ✨ button or Alt+O; streams the result live over SSE with reasoning shown first, and follows the session's default model with zero configuration or any OpenAI-compatible endpoint.
インストール
dsh plugin --profile web add github:winditer/dsh-prompt-optimizerREADME
dsh-prompt-optimizer
English | 中文
One-click prompt polishing for the DSH composer: select nothing, just type a draft and press ✨ (or Alt+O) — the plugin rewrites it into a clearer, better-structured prompt. Zero-config by default: it follows the current session's model through the harness host services, so no API key is needed. A self-configured OpenAI-compatible endpoint is supported as an alternative.
Features
- One-click optimize — a ✨ button on the composer's right;
Alt+Owhile the composer is focused does the same - True streaming preview — real SSE over the harness
webServer: everytext-deltafromllm.streamis pushed immediately and rendered token by token; reasoning is streamed first, so you watch the model think while it works - Zero-config default — reuses the current session/agent default model (host
agentDefaultModel+llmservices), no API key required - Custom endpoint mode — uncheck "follow session model" and plug in any OpenAI-compatible
/chat/completionsendpoint (base URL + key + model) - Action row on completion — replace the draft in place, copy, re-optimize, or dismiss
- Bilingual UI — follows the DSH language (中文 / English) live, no reload
- Self-hosted config — settings persist in
~/.dsh/prompt-optimizer-config.jsonvia a loopback RPC channel, independent of the host settings registry - Dark-mode ready — all colors follow DSH theme variables; fixed brand blue + white text in deep-night mode
- Local-only credentials — the API key (custom mode only) lives in the local config file and goes only to the endpoint you configured
Screenshots
Requirements
- DSH with a
webordesktopprofile - Node.js
^22.19.0or>=24.0.0(only needed to build from source) - pnpm is recommended when installing into a profile
Install
The bundle entry (
id: prompt-optimizer) is self-declared by this package'scordis.patch.yml— no manual patch file is needed.
From npm
dsh plugin --profile desktop add dsh-prompt-optimizer
For a web profile, use --profile web. Restart DSH (quit fully and reopen), then a ✨ button appears to the right of the composer.
From source (development)
git clone https://github.com/winditer/dsh-prompt-optimizer.git && cd dsh-prompt-optimizer
npm install
npm run build # produces dist/client.js
dsh plugin --profile desktop add . # links the workspace into the profile by package name
Or install by hand: in the target profile's package.json (e.g. ~/.dsh/profiles/desktop/package.json):
{
"dependencies": {
"dsh-prompt-optimizer": "link:/absolute/path/to/dsh-prompt-optimizer"
// ...
},
"dsh": {
"profile": {
"bundles": [ /* ... */, "dsh-prompt-optimizer" ]
}
}
}
then pnpm install inside the profile directory and restart DSH.
Uninstall
Remove dsh-prompt-optimizer from the profile's dependencies and dsh.profile.bundles, clean up the installed package, and delete the config file ~/.dsh/prompt-optimizer-config.json if you no longer need it.
Usage
- Type a draft in the composer, click ✨ (or
Alt+O) — the preview card appears over the composer - While optimizing: reasoning text scrolls in secondary color first, then the polished prompt streams in token by token
- When done: 替换草稿 writes the result into the composer in place · 复制 copies it · 重新优化 re-runs · 放弃 dismisses
- The preview belongs to the session where you started it: switching sessions hides it, switching back restores it
Configuration
Open 设置 → 通用设置 → Prompt 优化:
| Setting | Default | Meaning |
|---|---|---|
| 使用当前会话模型 | on | Follow the session/agent default model (zero-config). Off: enable the fields below |
| 接口地址 (base URL) | https://api.deepseek.com | Any OpenAI-compatible /chat/completions endpoint |
| API Key | — | Your key for the custom route (ignored in follow mode) |
| 模型名 | deepseek-v4-flash | Model name (ignored in follow mode) |
Settings are saved in ~/.dsh/prompt-optimizer-config.json (same directory as other DSH config; removed with the plugin).
Custom endpoints must support CORS and SSE streaming (official DeepSeek, OneAPI-style gateways work).
Architecture
Two halves, one package:
- Host half —
lib/index.js. Persists config over a loopback RPC channel (/dsh-prompt-optimizer,get/set), and registers an HTTP JSON API at/dsh-prompt-optimizer/apithrough the harnesswebServerservice. Runs the session-default optimization viallm.stream; background streams live in an in-memoryMapcleared on unload. - Client half —
src/*.ts, bundled todist/client.js(esbuild, wrapped in__ModuleLoader__.load({ id: "dsh-prompt-optimizer", … }); the id must equal the installed package name). Renders into theconversation.input.rightbutton,conversation.input.overlaypreview card andsettings.general.itemrow; talks to the host withfetchPOSTs.
Host API
All endpoints are POST /dsh-prompt-optimizer/api/<method>; every response is { ok: true, value } or { ok: false, error }.
| Method | Body | Returns |
|---|---|---|
sessionModel | {} | { provider, model, reasoningEffort? } — the session's default model |
optimize.stream | { provider, model, text, system?, reasoningEffort? } | text/event-stream — event: reasoning frames first, then event: delta per token, event: done at the end |
optimize.start | { provider, model, text, system?, reasoningEffort? } | { taskId } — background accumulation (fallback path) |
optimize.poll | { taskId } | { done, text, error? } — accumulated text while streaming |
optimize.abort | { taskId } | { ok } |
Protocol details: only POST is accepted (405 otherwise); the body is JSON with a 1 MB cap; unknown methods return 404.
Security notes
- Default route sends no credentials — it reuses the harness's configured provider.
- The custom-mode API key stays local (
~/.dsh/prompt-optimizer-config.json), and only goes to the endpoint you configured. - Optimizations appear only in the preview card; the polished text reaches a session only if you press 替换草稿.
Development
npm run build # esbuild: src/index.ts → dist/client.js (__ModuleLoader__ bundle)
npm test # node runner over tests/entry.ts (state machines, channels, SSE parser)
Project layout
src/index.ts Client entry — slot wiring, RPC/config glue, host probes
src/OptimizeButton.tsx / PreviewCard.tsx / SettingsRow.tsx
src/optimizer.ts Config defaults, system prompts, OpenAI-compatible fetch/SSE client
src/session-optimizer.ts Host channel: sessionModel + SSE stream + fallback poll
src/preview-state.ts Preview card state machine (pure reducer)
src/preview-bus.ts Module-level event bus shared by button / card / orchestration
lib/index.js Host half — config persistence + HTTP JSON API (makeHandler + createApiRoute)
dist/client.js Built client bundle (__ModuleLoader__ format, load id = dsh-prompt-optimizer)
cordis.patch.yml Bundle entry declaration (insert: { id: prompt-optimizer, name: dsh-prompt-optimizer })
scripts/build.mjs Build script (esbuild + bundle wrapper)
tests/entry.ts Unit + integration tests (61)
assets/ Screenshot
Gotchas (learned the hard way)
-
Bundle id must equal the package name —
arrive()throwsbundle loaded without registering <id>otherwise.scripts/build.mjshardcodes the correct id. -
Profile bundles don't get the cordis
timerservice — use plain browsersetInterval/setTimeout(disposed in React effect cleanup), exactly like the siblingdsh-elfbundle. -
Do not use
session.create/forkfor generation — a background session never executes (the renderer's fabricated ids are silently rejected, forked sub-sessions don't trigger the model), which manifested as "optimizing forever". Drive the model from the host half viallm.streaminstead. -
Do not run the streaming protocol over
connection.rpc.call— on desktop the renderer's rpc.call hangs on the second call within one flow (verified:sessionModelok, next call never arrives). Host channels go over HTTP (webServer). -
Prefer
link:overfile:when installing a workspace copy —file:copies files, so edits/rebuilds go stale. -
Client changes go live on page refresh; host changes require a full DSH restart.
-
A broken build script silently keeps the old bundle —
npm run buildmust print✓ Built; if it only prints a Node version banner, the script is failing (a past regression left a staledist/client.jsthat looked "current"). -
Fresh publishes can trip the profile's
minimumReleaseAgepolicy — if the profile enforces pnpm's release-age supply-chain check, a version published less than ~24 h ago failsdsh plugin … add <pkg>withERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION. Add the exactname@versiontominimumReleaseAgeExcludein the profile'spnpm-workspace.yaml(and keep the entry current when you release a new version):minimumReleaseAgeExclude: - dsh-prompt-optimizer@2.0.0