Skip to content

The entry point

The dsh command is a single bin declared in apps/cli/package.json:

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:

ModeWhat it does
profileBoot a named profile (runProfile)
dump-configPrint the composed profile tree and exit, no boot
pluginManage 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

FlagScopeEffect
--profile <name>launcherBoot the profile under $DSH_HOME/profiles/<name>
--patch <path>launcherExtra patch-list overlay applied after the profile & home layers (repeatable)
--dump-configlauncherPrint the composed tree (including the user layer & --patch) and exit
--dump-default-configlauncherPrint bundle layers only, no user layer or overlays
websubcommandAlias of --profile web
--version / -VlauncherPrint the version and exit
--helpapp-ownedEach app prints its own
--host / --port / --trusted-hostweb appParsed 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):

FieldOwnerMeaning
dsh.profile.bundlesa profile (package.json)Ordered bundle layer list (package names)
dsh.bundle.patcha 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:

json
{ "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }

and a profile directory's package.json (written by initProfile):

json
{ "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:

BundleLayerContent of its patch file
@deepseek-ai/dsh-basefirst of every profiletimer & hmr, llm, session, typert, agent, agent-loop, jobs, settings, credentials, persistence, sandbox, approval, subprocess, tools, prompt sections, shell stacks
@deepseek-ai/dsh-web-appover baseweb host rows (webserver, api gateway, workspace, projection cache, storage), browser plugin roster, web-runtime glue, web-startup command line
@deepseek-ai/dsh-headlessover basea 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.tsapps/cli/src/profile-boot.ts:

text
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 HMR

The 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):

  1. Each bundle in dsh.profile.bundles order
  2. Profile's own cordis.patch.yml
  3. Home-level cordis.patch.yml
  4. --patch overlays
  5. Telemetry switch / shipped preset root

The Profile class and patch composition

packages/boot/app-boot/src/profile.ts exposes the launcher-facing API:

FunctionRole
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:

text
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 leaves

Reconciliation 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.