DEV Community

Cover image for What Actually Happens When You Run `npx @deepseek-ai/dsh web`
GEEK
GEEK

Posted on

What Actually Happens When You Run `npx @deepseek-ai/dsh web`

https://github.com/deepseek-ai/deepseek-harness
DeepSeek Harness (DSH) got a lot of attention for being usable with a single line:

npx @deepseek-ai/dsh web
Enter fullscreen mode Exit fullscreen mode

That opens a local Web UI at http://127.0.0.1:3080. It genuinely works. But "it started" and "I know what I'm running" are different things, and the answers to the obvious follow-up questions — why is it asking for an API key, why is the composer locked, what does "everything is a plugin" mean for me, is a random plugin safe — are spread across four different files in the official repo.

I've been assembling a reference site for DSH, which meant reading all four closely. This post connects them. Every claim below is from the official docs; I'm just holding the pieces together.

1. dsh is a launcher, not the app

The CLI spec describes itself precisely:

The dsh command is the product launcher for profiles: ordered stacks of plugin-bundle patch layers under the user's own overrides.

When you run npx @deepseek-ai/dsh web:

  • The launcher parses only its own flags (src/args.ts owns the grammar; bin.ts loads only the selected runner). Invalid commands, options from another mode, config errors, and boot failures exit nonzero — good news if you script around it.
  • web is an alias for --profile web, which boots a profile living under $DSH_HOME/profiles/web.
  • The web and headless profiles auto-initialize from shipped templates on first use. That's why zero-config works. Any other profile name must be created through dsh plugin.
  • A detail people miss: your current working directory becomes the default filesystem root. "The dsh process uses its invoking directory as the default filesystem location." Where you launch from is not a neutral choice.

2. "Everything is a plugin" is a layering system, not a slogan

Each profile directory holds a package.json (with a dsh.profile manifest listing ordered bundles) and a cordis.patch.yml (your override layer). The final plugin tree composes in this order:

each bundle's patch, in dsh.profile.bundles order
→ the profile's own cordis.patch.yml
→ the home-level $DSH_HOME/cordis.patch.yml
→ --patch command-line overlays
Enter fullscreen mode Exit fullscreen mode

Bundle resolution: built-ins first (@deepseek-ai/dsh-base, dsh-web-app, dsh-headless, installed with dsh itself), then the profile's own node_modules — which is exactly where third-party plugins land.

This architecture explains both halves of the DSH experience:

  • The flexibility: swapping models, tools, session storage, even the UI is a config change, not a fork.
  • The warning: the README capitalizes "THERE WILL BE COMPATIBILITY-BREAKING CHANGES." When the whole product is a composed tree, any layer's interface change ripples upward. In developer preview, that's structural, not attitude.

3. The three gates of first run

Gate 1: API key. Settings → Models, paste, save. Three details worth knowing:

  • No restart needed. "Model changes take effect on the next request without restarting the server." Just use it.
  • Keys are write-only: after saving, the UI only ever shows a redacted descriptor. You can't read it back.
  • The key lives in $DSH_HOME/.credentials.yaml; settings retain only a credential reference. Keep that in mind before you sync or screenshot config directories.

Gate 2: workspace. The guide is blunt: "The session composer remains unavailable until a workspace is selected." The composer is locked, not broken — click Choose workspace, add the directory you launched dsh from, and select it. The agent reads, edits, and runs commands around that workspace, and the UI asks for approval on operations that require it under the active permission policy.

Gate 3: the first task. The official guide suggests:

Summarize this repository and identify its main packages.

It's a good first prompt because it's read-only and easy to verify by hand. Get one read-only task fully working before you let an agent touch code.

4. The vision-model gotcha

If you add a custom model by hand, DSH treats it as text-only by default: "A model you entered by hand is treated as text-only until it says otherwise." To accept images, declare the modality on the model in $DSH_HOME/settings.yaml:

llm-pi-ai:
  providers:
    my-gateway:
      apiKeyEnv: GATEWAY_API_KEY
      api: openai-completions
      baseURL: https://gateway.example/v1
      models:
        - id: vision-preview
          input: [text, image]
Enter fullscreen mode Exit fullscreen mode

And the sharper edge, straight from the docs: DeepSeek's own chat-completions route is text-only and cannot be configured otherwise. Vision requires a custom provider with a vision model, or a community plugin. Most people learn this only after an attachment gets refused.

5. Third-party plugins are npm packages

The plugin command's mechanics are one sentence in the CLI spec:

dsh plugin --profile <name> <pnpm args>
Enter fullscreen mode Exit fullscreen mode

It forwards your args to pnpm inside the profile directory. A third-party plugin is a regular npm dependency running under your user permissions. Same threat model as any npm install: think postinstall scripts, env vars, network access. The Awesome list explicitly says listing ≠ security review.

Before installing, check at least: the repo's activity, the license, what its install scripts do, and which capabilities it actually needs (files, shell, network, credentials). The official discovery surface is the GitHub dsh-plugin topic — the README asks plugin authors to tag their repos with it.

6. Preview-era survival kit

See the composed tree without booting anything:

dsh --profile web --dump-default-config   # what the default composition is
dsh --profile web --dump-config           # what YOUR composition actually is
Enter fullscreen mode Exit fullscreen mode

This is the most underused debugging command in the whole CLI. "Is my config even in the tree?" — answered statically.

One-shot, no UI:

dsh --profile headless "Inspect the repository and fix the failing tests."
Enter fullscreen mode Exit fullscreen mode

Runs a fresh persisted session, prints the final answer, exits. Scriptable by design.

Python SDK: pip install deepseek-harness-sdk ships a bundled runtime — no system Node.js required. Two caveats from the guide: the checked-in example composition runs danger-full-access (use a disposable checkout or container), and its persistent PTY backend needs POSIX, so no Windows agents in that composition.

Wrapping up

None of this is secret — it's all in the repo. It's just spread across the README, the CLI spec, and two guides, and nothing links the launcher to the first-run experience end to end. Once you see the shape — launcher → profile → layered plugin tree → workspace-gated session — the individual behaviors stop being surprising.

I maintain a community reference for exactly this kind of thing — install paths, a curated plugin directory with permission notes, compatibility and troubleshooting pages, organized around "finish your first real task": deepseekharness101.com. The plugin directory and troubleshooting section are the most-linked pages.

Docs will drift — it's a preview. The mechanisms won't. Good first run.

Top comments (0)