DeepSeek Harness dsh commands: web, plugin and --profile, with exit codes
dsh is the command-line launcher for DeepSeek Harness. Its commands fall into four families: dsh web boots the Web UI, dsh --profile headless "job" runs a one-shot session, dsh --profile <name> boots any profile, and dsh plugin manages plugins — the launcher parses only its own flags and hands everything after to the booted app. This dsh command list is organized by entry-command cheatsheet, argument ownership, dsh web flags, dsh plugin, config debugging and exit codes. Every command and flag comes from the official dsh CLI README and CLI behavior reference, so you can copy and run them as-is.
dsh commands at a glance: web, plugin and --profile entry modes
dsh does one thing: load the selected profile and hand the remaining arguments to the booted app. In the official docs' words, the command grammar lives in src/args.ts and src/bin.ts loads only the selected runner (source). The Entry modes table defines all four entry commands — the backbone of any dsh command list (source):
| Command | Purpose |
|---|---|
dsh --profile <name> | Boot the named profile under $DSH_HOME/profiles/<name> |
dsh --profile headless "job" | Run one fresh persisted session, print the final answer, exit |
dsh web | Hardcoded alias of --profile web, boots the Web UI |
dsh plugin --profile <name> <pnpm args> | Forward to pnpm inside the profile directory to manage plugins |
To get started, follow these three steps (source):
- Open a terminal and cd into the project directory you want as the workspace;
- Run
npx @deepseek-ai/dsh web(not globally installed) ordsh web(globally installed) and wait for the printed address; - Open the Web UI in a browser, add an API key, select the workspace and start.
Supporting facts from the same source (source):
- The invoking directory is the default workspace root in every mode.
- Auto-initialization: the
webandheadlessprofiles initialize from shipped templates on first use (web = base + web-app, headless = base + headless); any other missing profile fails loud with a hint to rundsh plugin --profile <name> add <package>(source). - Data directory: profiles live at
$DSH_HOME/profiles/<name>, where$DSH_HOMEdefaults to~/.dsh.
Argument ownership: launcher flags vs flags handed to web or headless
dsh parses only its own flags; from the first token it does not recognize onward, every argument is handed verbatim to the booted profile. The official README shows the boundary with (source):
dsh --profile web --port 8080 # --port belongs to the web app
dsh --profile headless "run the tests" # the quoted job is the headless app's positional argument
dsh --profile web --help # prints the web app's help and boots nothing
dsh --help # prints the launcher's own help
Edge cases worth knowing (source):
-V/--versionworks only before the boundary:dsh --versionprints the launcher version; past the boundary it is handed to the app.- A literal
--needs two: the launcher consumes one--, so an app argument that must arrive as a literal--needs-- --. - The web app's own command line is
--host,--portand repeatable--trusted-host(source). - The headless app's own command line is a single positional task text; an invocation without a task is a usage error.
dsh web flags: changing the port, trusted hosts and boot examples
dsh web is the hardcoded alias of --profile web and boots the Web UI; it supports --host, --port and --trusted-host. The web runner serves http://127.0.0.1:3080 by default (source). Common forms:
# Minimal boot (equals dsh --profile web)
dsh web
# Change the port: --port is a web-app flag
dsh web --port 8080
# Boot with an extra local patch layer
dsh web --patch ./extra.cordis.yml
# Print the composed config tree without booting
dsh web --dump-config
# Show the web app's full flags
dsh web --help
What to know when using it (source):
- Default 3080: after a successful boot, open
http://127.0.0.1:3080in a browser; on first use add an API key in Settings and select a workspace — see How to use npx @deepseek-ai/dsh web. --host 0.0.0.0is not supported yet and exits with a usage error; to constrain source hosts use the repeatable--trusted-hostflag to add named authorities.- npx equivalent: without a global install, prefix every command with
npx @deepseek-ai/dsh, e.g.npx @deepseek-ai/dsh web --port 8080.
dsh plugin: add, remove, why, update and GitHub source installs
dsh plugin forwards whatever follows --profile <name> to pnpm inside the profile directory, so pnpm verbs such as add, remove, why and update work unchanged. If the profile is missing it first initializes from a template (shipped for web, @deepseek-ai/dsh-base alone for other names) and then runs pnpm (source). Examples:
# Install an npm plugin into the web profile
dsh plugin --profile web add dsh-plugin
# Install from a GitHub repo (source install, active right away)
dsh plugin --profile tui add github:deepseek-harness/turtle-ui
# Remove a plugin
dsh plugin --profile tui remove turtle-ui
# Find out why a dependency is installed
dsh plugin --profile web why dsh-plugin
# Update plugins in a profile
dsh plugin --profile web update
Two things to remember when installing from GitHub source (source):
- The first git add can fail: git-hosted plugins that ship sources build through a
preparescript during install, which pnpm ≥10 blocks by default; copy the printedallowBuildskey into the profile'spnpm-workspace.yamland rerun. - Local paths win: relative specs (
.,../pluginand theirfile:/link:forms) anchor to the invoking directory, so runningdsh plugin --profile web add .inside a plugin checkout installs that checkout, not the npm package.
After each successful run, dsh.profile.bundles reconciles against the installed state: a dependency that newly declares dsh.bundle.patch joins the layer stack, and a removed dependency leaves it (source). If you would rather click than memorize flags, install DSH Plugin Hub and use Settings > Plugin market in the Web UI for visual installs, uninstalls and updates.
Debugging config: --patch and --dump-config / --dump-default-config
dsh composes its tree by layering bundle patches, the profile's cordis.patch.yml, the home-level $DSH_HOME/cordis.patch.yml and --patch overlays, with later layers winning; use the dump flags to see the result without booting (source). Debug with:
# Print only the bundle layers (no profile/home/patch overlays)
dsh --profile web --dump-default-config
# Print the full composed tree: bundle + profile + home + --patch
dsh --profile web --patch ./extra.yml --dump-config
- Plugin installed but not taking effect? Run
dsh --profile web --dump-configand check which file supplied each row and which overlay changed it — the comments name the source file. - Want to change a setting temporarily without editing files? Pass
--patch ./extra.yml; it is the last layer and wins. - Two limits: a dump never runs app command-line providers, so an invocation that carries app arguments is rejected; it also does not actually boot the Web UI.
Exit codes: how to tell from a script whether dsh succeeded
dsh expresses results through exit codes: a headless job exits 0 on completed and 1 otherwise, help requests exit 0, rejected arguments exit nonzero, SIGINT reports 130 and SIGTERM exits 0. Invalid commands, options from another mode, config errors and boot failures all exit nonzero (source), so check $? in a script instead of parsing text output:
| Scenario | Exit code |
|---|---|
| Headless job completed | 0 |
| Headless job ended not completed | 1 |
--help request | 0 |
| Rejected arguments / invalid command / config error / boot failure | nonzero |
| SIGINT (graceful drain after Ctrl+C) | 130 |
| SIGTERM (supervisor's ordinary stop) | 0 |
| Second signal | force exit immediately |
A complete example for scripts and automation — run a one-shot job and branch on the result:
dsh --profile headless "run the tests"
if [ $? -eq 0 ]; then
echo "job completed"
else
echo "job failed with exit code $?"
fi
Note that web gives the plugin tree up to five seconds to dispose on SIGINT/SIGTERM, so the first Ctrl+C is a graceful drain, not an instant kill (source). When you put dsh into a script or CI, checking the exit code is the most reliable signal — use the cheatsheet above for dsh web, dsh plugin and dsh --profile, and turn to DSH Plugin Hub's UI when you prefer clicking over typing.
Sources: dsh CLI README, dsh CLI behavior reference, official Quickstart
FAQ
dsh is the command-line launcher of DeepSeek Harness, with four official entry commands: dsh web boots the Web UI (an alias of dsh --profile web), dsh --profile headless "job" runs one session and exits, dsh --profile <name> boots any installed profile, and dsh plugin --profile <name> <pnpm args> manages plugins. Not installed globally? Prefix any command with npx @deepseek-ai/dsh, for example npx @deepseek-ai/dsh web.
Because dsh is a launcher and parses only its own flags: everything after the first token it does not recognize goes to the booted DeepSeek Harness app. So --profile belongs to the launcher and --port belongs to the web app, and dsh --profile web --port 8080 boots the Web UI on port 8080. A literal -- inside app arguments needs -- --; -V/--version also works only before that boundary.
dsh web boots the Web UI, which supports --host, --port and a repeatable --trusted-host; dsh web --port 8080 changes the listening port. The web app serves http://127.0.0.1:3080 by default, and the CLI does not support --host 0.0.0.0 yet, exiting with a usage error. To constrain source hosts use --trusted-host, or prefix with npx when dsh is not installed globally.
Both work. dsh plugin forwards whatever follows --profile <name> to pnpm inside that profile directory, so pnpm verbs like add, remove, why and update are available unchanged: dsh plugin --profile web add dsh-plugin installs an npm package, and dsh plugin --profile tui add github:deepseek-harness/turtle-ui installs from GitHub source. If the first git add is blocked by pnpm, copy the printed allowBuilds key into the profile's pnpm-workspace.yaml and rerun.
Check the exit code instead of parsing output: a headless job exits 0 on completed and 1 otherwise, help requests exit 0, and rejected flags, invalid commands, config errors and boot failures all exit nonzero. SIGINT from Ctrl+C reports 130 and SIGTERM exits 0. In a script, branch on $? with an if statement to tell success from failure.
Related Terms
- dsh
- dsh is the command-line launcher (product launcher) of DeepSeek Harness: it loads the selected profile and hands the remaining arguments to it, with official entry commands grouped into dsh web, dsh --profile and dsh plugin.— dsh CLI README
- profile
- A profile is an ordered stack of plugin-bundle patch layers stored under $DSH_HOME/profiles/<name>; profiles such as web and headless auto-initialize from shipped templates on first use.— dsh CLI README
- dsh plugin
- dsh plugin is dsh's plugin-management subcommand; it forwards whatever follows --profile <name> to pnpm in that profile directory, so pnpm verbs such as add, remove, why and update all work.— dsh CLI behavior reference
- --dump-config
- --dump-config is a dsh flag that prints the composed config tree without booting, useful for debugging whether plugin patches took effect; --dump-default-config prints only the bundle layers.— dsh CLI behavior reference
Sources
- dsh CLI README· deepseek-ai
- dsh CLI behavior reference· deepseek-ai
- DeepSeek Harness docs - Quickstart· deepseek-harness