The entry point
The dsh command is a single bin declared in apps/cli/package.json:
{ "bin": { "dsh": "lib/bin.js" } }The dsh bin (apps/cli/src/bin.ts) reads the app version from its own checked-in package.json, parses argv with parseDshArgs, and dispatches on the resulting invocation mode:
| Mode | What it does |
|---|---|
profile | Boot a named profile (runProfile) |
dump-config | Print the composed profile tree and exit, no boot |
plugin | Manage a profile's plugins by forwarding to pnpm |
Each mode is dynamically imported so unrelated modes stay out of each other's dispatch path.
The two-tier argument model
A key design decision splits the command line in two:
- The launcher parses only its own flags:
--profile, repeated--patch,--dump-config,--dump-default-config. - Everything after those is handed verbatim to the booted tree as
ctx.cmdlineArgs, where each injected app plugin parses its own flag family and prints its own--help.
This is why dsh --profile tui --resume abc boots the tui profile with inner args ['--resume', 'abc'], and dsh --profile web --help prints the web app's help, not the launcher's. App flags never reach the launcher, and the first token the launcher does not recognize starts the inner arguments.
apps/cli/src/args.ts builds this with commander (program.allowUnknownOption().passThroughOptions().enablePositionalOptions()), and --patch is deliberately a non-variadic repeatable collector so it never swallows inner arguments.
web is a hardcoded alias for --profile web; plugin manages a profile's dependencies by forwarding its arguments to pnpm inside the profile directory.
Key flags
| Flag | Scope | Effect |
|---|---|---|
--profile <name> | launcher | Boot the profile under $DSH_HOME/profiles/<name> |
--patch <path> | launcher | Extra patch-list overlay applied after the profile & home layers (repeatable) |
--dump-config | launcher | Print the composed tree (including the user layer & --patch) and exit |
--dump-default-config | launcher | Print bundle layers only, no user layer or overlays |
web | subcommand | Alias of --profile web |
--version / -V | launcher | Print the version and exit |
--help | app-owned | Each app prints its own |
--host / --port / --trusted-host | web app | Parsed by web-startup in packages/bundle/web-app/src/startup.ts via parseCmdline, provided as ctx.webStartup |
There is no launcher-level --port. The port is an app-owned flag: the web-startup provider injects ctx.cmdlineArgs, parses its commander program, and publishes webStartup. Flag-configured rows read it through lazy config, so nothing binds a port before argument resolution and dsh --profile web --help starts no server.
The dsh field schema in package.json
Profiles and bundles each declare a dsh section (packages/boot/app-boot/src/profile.ts):
| Field | Owner | Meaning |
|---|---|---|
dsh.profile.bundles | a profile (package.json) | Ordered bundle layer list (package names) |
dsh.bundle.patch | a bundle (package.json) | The bundle's patch file, relative to its package root |
A manifest may declare both roles. For example packages/bundle/base/package.json:
{ "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }and a profile directory's package.json (written by initProfile):
{ "name": "dsh-profile-web", "private": true,
"dependencies": {},
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] } } }A profile directory sits at $DSH_HOME/profiles/<name> and holds its package.json, a cordis.patch.yml (the user's own patch layer), and a pnpm-workspace.yaml (so out-of-tree plugins install with the hoisted linker).
A bundle is a distribution format for Cordis config rows
The three shipped bundles — packages/bundle/base, packages/bundle/headless, packages/bundle/web-app — each export a cordis.patch.yml:
| Bundle | Layer | Content of its patch file |
|---|---|---|
@deepseek-ai/dsh-base | first of every profile | timer & hmr, llm, session, typert, agent, agent-loop, jobs, settings, credentials, persistence, sandbox, approval, subprocess, tools, prompt sections, shell stacks |
@deepseek-ai/dsh-web-app | over base | web host rows (webserver, api gateway, workspace, projection cache, storage), browser plugin roster, web-runtime glue, web-startup command line |
@deepseek-ai/dsh-headless | over base | a direct core Agent/Session runner with no Host, HTTP, or browser layer |
A bundle is "a distribution format for Cordis config rows and the code they mount" precisely because a patch targets a row by id and replaces its whole config (or inserts new rows), so whatever a bundle inserts stays patchable by the layers above it.
What happens from dsh web to a running server
Walking apps/cli/src/bin.ts → apps/cli/src/profile-boot.ts:
dsh web
├─ loadLayeredEnv('dsh') parse .env (invoking dir + Harness home),
│ reject bootstrap-only names, freeze snapshot
├─ runProfile({ environment, profile:'web', patchFiles, args })
│ ├─ composeProfile('web', …)
│ │ ├─ prepareProfile -> healProfilesModuleFallback(INSTALL_ANCHOR)
│ │ │ (flat node_modules fallback for in-box plugins)
│ │ ├─ loadProfile -> resolve bundle layers (base, web-app) + load
│ │ │ user cordis.patch.yml
│ │ ├─ homePatches ($DSH_HOME/cordis.patch.yml)
│ │ ├─ overlays (--patch files, shipped preset root, telemetry switch)
│ │ └─ rows map (composeEntries over the empty root)
│ ├─ createProcessShutdown (bounded exit controller)
│ ├─ process.on SIGTERM/SIGINT + installFailLoud
│ └─ boot(NAME, rootConfig, structuredClone(allPatches), prepare)
│ ├─ new Context() // Cordis backbone
│ ├─ ctx.baseUrl / provide dshHomePath
│ ├─ ctx.plugin(Loader) // cordis-plugin-loader
│ ├─ prepare(hostCtx) // BEFORE any config entry mounts:
│ │ provide env snapshot + provideCmdline(args, exit)
│ ├─ mountRootInclude // 'cordis:include' + 'cordis:group'
│ └─ await loader; assertEntriesActivated -> settled Context
└─ watchUserPatches // keep cordis.patch.yml hot via HMRThe root config it boots is profile/cordis.yml, an empty entry list (PROFILE_ROOT_CONFIG in profile-boot.ts), rewritten every boot — because the vendored Loader's tree write-back can bake composed rows into it, which would duplicate every bundle insert on the next boot. The file exists only so the Loader has a real include root to anchor baseUrl at the profile directory.
Patch order (later wins):
- Each bundle in
dsh.profile.bundlesorder - Profile's own
cordis.patch.yml - Home-level
cordis.patch.yml --patchoverlays- Telemetry switch / shipped preset root
The Profile class and patch composition
packages/boot/app-boot/src/profile.ts exposes the launcher-facing API:
| Function | Role |
|---|---|
resolveProfileDir(name, home) | $DSH_HOME/profiles/<name>, rejects path separators |
initProfile(dir, bundles) | First-use template: manifest, patch template, pnpm-workspace.yaml |
loadProfile(binName, name, installAnchor) | Resolve bundle layers + parse the user patch layer |
resolveBundleDir(...) | Bundle resolution, installation anchor first, then the profile dir |
composeEntries(layers) | One applyEntryPatches over an empty root — what boot mounts |
healProfilesModuleFallback(...) | Maintain the flat profiles/node_modules symlink fallback |
loadOverlayPatches and loadOptionalPatches parse a top-level YAML array of loader patch entries (with the include's !!js expression dialect and relaxed schemastery schema); a missing optional file means "no layer", while a missing required overlay throws — the caller named that file.
--dump-config (apps/cli/src/dump-config.ts) reuses the same composition so it prints exactly what the invocation would mount. It never evaluates !!js (renderConfigDump in index.ts prints expressions verbatim, unevaluated) and never boots the tree, so config dumps cannot show what app flags would decide.
Out-of-tree plugins: dsh plugin --profile <name>
dsh plugin (apps/cli/src/plugin.ts) is a thin pnpm forwarder:
dsh plugin --profile tui add some-cordis-plugin
├─ initProfile if needed
├─ pnpm <args…> in the profile directory
└─ reconcilePlugins: any installed dependency whose manifest declares
dsh.bundle joins dsh.profile.bundles; a removed/bundle-less one leavesReconciliation is by installed state, not dependency diff, so update activates a package that gained its dsh.bundle declaration in a newer version. Out-of-tree plugins find Cordis and the Service Definition packages through the healed profiles/node_modules fallback, so every plugin shares the installation's single Cordis instance instead of a duplicate.
Further reading
- Architecture at a Glance — the plugin tree and host/client split this boot populates.
- The extension (Cordis) system — what a mounted plugin row actually is.
- Source:
packages/boot/app-boot/src/profile.ts,packages/boot/app-boot/src/index.ts(boot,mountRootInclude,renderConfigDump). - Source:
apps/cli/src/args.ts,apps/cli/src/profile-boot.ts,apps/cli/src/plugin.ts,apps/cli/src/dump-config.ts. - Repo docs:
docs/architecture.md(Profiles and bundles section). - Bundle manifests:
packages/bundle/base/package.json,packages/bundle/web-app/package.json,packages/bundle/headless/package.json.