DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
H

dsh-hint-shell

Hint Shell

Composer hint for shell commands in DeepSeek Harness: appends "type ! for shell commands" to the empty composer placeholder and shows a gray hint once ! is typed.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:Mooling0602/dsh-hint-shell#82a962bb6cd35bc905bd180251a5662315446dd3
READMECompatibilityVersions

Compatibility and provenance

Hint Shell is published as dsh-hint-shell and currently resolves to version 0.1.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/23/2026

Versions

0.1.0stable
10/23/2026

Related plugins

Loading related plugins…

Latest
0.1.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
10/31/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in ui-customization.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Experimental Session Titles Bundle@deepseek-ai/dsh-experimental-session-titles-bundleRefresh session titles as new human prompts arrive.Personalized Recommendations@deepseek-ai/dsh-personalized-recommendationsPersonalized pages and built-in plugin recommendations offered as cards below answersExperimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profileExperimental Web profile layer for Agent Teams Remote and UI plugins

README

dsh-hint-shell

A DeepSeek Harness plugin that makes ! a shell prefix in the composer.

  • Empty composer — appends ,输入 ! 执行 shell 命令 to the shipped placeholder, so it reads 发消息或创建任务, / 调用指令, @ 文件或对话,输入 ! 执行 shell 命令.
  • After typing ! — shows a gray hint 输入 shell 命令… right after the !.
  • On submit — a line like !ls runs as a real shell command in the session's working directory. Its output is recorded in the session as a tool call and result, so the next real request already sees it. No model request is made for the ! line itself.

Nothing else changes: no new button, no replaced component, no altered keyboard behaviour.

! follows the same convention as /

The Harness treats a trigger symbol as a trigger only once something follows it: parseCommand requires at least one name character after /, and the composer's matchEnter bails when the token is empty. A lone / is therefore ordinary text, not a command.

! behaves the same way. A lone ! is prose and is sent to the model like any other message; only ! with a command behind it is shell intent. Both !ls and ! ls mean ls.

How it works

The composer effects reuse machinery the shipped composer already has.

Idle hint — the empty-composer placeholder is produced by a locale lookup of conversation/placeholder.default. LocaleRuntime.prototype.translate is a prototype method, so the plugin wraps it and appends one clause to that key (and to placeholder.hero, the same composer in its empty-session layout). Every other namespace and key passes through untouched.

Armed hint — the shipped stylesheet already paints this exact kind of message:

.input p:last-child::after {
  content: var(--dsh-composer-hint);
  color: var(--dsw-alias-label-caption);
}

Upstream only populates that property for a native command claim, which a ! prefix cannot produce. Custom properties inherit, so setting --dsh-composer-hint on the composer card is enough: the shipped rule then draws the text immediately after the typed !, in the shipped caption colour. The plugin adds no DOM, does no measurement, and does no positioning.

The draft itself comes from [data-composer-input], the editable surface the composer already publishes, so this plugin has no companion plugin and no dependency on internal trigger internals.

Copy

Copy lives in the plugin's own dsh-hint-shell locale namespace, so registration can never collide. The locale service's register is append-only and throws on a duplicate (namespace, locale) pair, which is why overriding the conversation namespace from a plugin is not possible.

zhen
idle clause,输入 ! 执行 shell 命令, type ! for shell commands
armed hint输入 shell 命令…Enter a shell command…

Edit src/copy.ts to change them.

Execution

The host half hooks agent/pre-step, the last point before the loop dispatches a request, and its reject decision stops that request. A ! line therefore costs zero tokens.

Recording the outcome needs a whole tool exchange, not one event. The model-visible surface carries tool/result but not tool/call, and a tool result with no preceding assistant tool-call block is a message answering a call that was never made — which a provider rejects. The records are written the way the agent loop writes its own, and in the shape the session's format v4 reader validates when the session is reopened:

  1. step/start — agent/pre-step runs before the loop opens its step, and v4 requires every surface message to belong to an open step. The plugin opens one for the pair.
  2. system/message with empty content — only when the surface is still empty. V4 protects the first system message as the surface head, and a later system/message cannot be appended without one. A ! line can be the first thing a session ever does, so the head is reserved; empty content derives to no model message and renders no transcript row.
  3. assistant/message carrying the tool-call block (surface event, identified message)
  4. tool/call (log-only, for the UI and replay)
  5. tool/result (surface event: an identified tool-role message whose source.callId matches its toolCallId, citing the tool/call seq as its source event)
  6. step/end — closes the synthetic step so the loop's turn/end stays legal

Every message carries a fresh UUID id and a producer-owned source.kind, which is what the v4 reader checks on reopen. Omitting any of this is not a cosmetic problem: the live append succeeds, then the persistence encoder refuses the row, the buffered batch never reaches the log, and the turn fails with a format error such as format v4 tool/result at seq 8 requires a first-class message with a string id.

Because these are appends to the durable log and the step was rejected, the command's output sits in the history for the next request without one being made for it.

Commands run through /bin/sh -c, so quoting, pipes, and globs behave as in a terminal rather than being re-implemented. Output is capped at 30 000 characters, and a non-zero exit or a signal is reported as a tool error.

subprocess is read optionally rather than declared in inject: a hard dependency would make Cordis refuse to mount the whole plugin when no provider is composed, taking the composer hints down with it. Without a provider the hints still work and a ! line records a tool error naming the missing service.

Development

Sources are TypeScript; both shipped JavaScript files are generated.

npm install          # typescript + esbuild + @types/node
npm run typecheck    # tsc --noEmit
npm run build        # src/ -> client.js, host.ts -> index.js
npm test             # composer, shell-line, and installed-Harness format tests

test/dsh-format.test.ts drives the host hook against an installed Harness: it appends through a real Session, then encodes, admits, and restores the log through the real format v4 reader, so a record shape the live app accepts but cannot re-open fails the suite. It resolves the harness packages from the host installation (DSH_MODULES, or the global npm root -g) and reports a skip when none is installed.

client.js must stay a single self-contained file: the Client module loader's require resolves host packages only and cannot resolve relative imports, which is why the build bundles src/ rather than shipping it. index.js is built rather than shipping .ts so the host never depends on the runtime having TypeScript stripping enabled.

Layout

PathPurpose
host.tsHost half: the ! pre-step hook (source).
index.jsGenerated Host module. Do not edit.
client.jsGenerated Client bundle. Do not edit.
src/copy.tsThe two strings, in both languages.
src/index.tsThe two composer effects: placeholder clause and hint painting.
src/exec.tsShell-line parsing, execution, and output formatting.
src/types.tsStructural types for the Client services used.
src/host-types.tsStructural types for the Host services used.
test/run.test.tsComposer integration test (fake DOM + fake locale).
test/host.test.tsShell-line and pre-step hook test.
test/dsh-format.test.tsInstalled-Harness test: append through a real Session, then reopen the log through the real v4 reader.
cordis.patch.ymlProfile bundle row.

Behaviour notes

  • The hint shows only while the draft is the bare !; typing a command clears it. No space is needed after the !.
  • The hint never shows on an empty composer (the placeholder covers that case) or in the middle of a sentence (hello !world is ordinary text).
  • The hint follows the active locale; switching language re-renders it.
  • Uninstalling restores the original translate.

Why a MutationObserver is involved

The editable surface belongs to Lexical, which commits typed text into its own paragraph nodes inside its update pass. That pass is not guaranteed to finish before the browser dispatches input, so a synchronous read of [data-composer-input] can still describe the previous character. Reading only on input made a bare ! appear one keystroke late — it showed up after the space that followed, and then appeared to "stick" when the draft was deleted back to !.

The plugin therefore repaints on the mutation Lexical actually makes, which is the authoritative signal that the text is in the DOM. A deferred read on input remains as a safety net. Both paths only schedule a paint, so an extra paint is cheap and idempotent.