dshkit

dsh 命令行完整参考:子命令、flag 与四种入口

dsh launcher 全部参考——--profile、--port、--patch、--dump-config、--dump-default-config、plugin 子命令、headless 单次执行、$DSH_HOME 路径、参数顺序与退出码。

更新于 2026-08-132 min
一句话结论

dsh 是个 launcher。它先解析自己的 flag——--profile、--patch、--dump-config、--dump-default-config、--help——遇到第一个不认识的 token,之后全部交给它启动的 app。dsh web 是 --profile web 的简写;dsh --profile headless "任务" 跑一次就退出;dsh plugin --profile <名字> 转发给 pnpm。

dsh 是 launcher,不是应用。它解析一个 profile、组装出插件树、然后启动这份组装描述的东西。CLI 里几乎 所有让人困惑的行为都源于这一点——尤其是参数顺序那条规则。

四种入口

入口调用方式行为
Profiledsh --profile <名字>启动 $DSH_HOME/profiles/<名字> 下的 profile
Webdsh web等价于 --profile web
Headlessdsh --profile headless "任务"执行单次会话,输出结果,终止
插件管理dsh plugin --profile <名字> <pnpm 参数>转发给 pnpm,管理该 profile 的插件

Launcher flag

Flag用途
--profile <名字>加载哪个 profile
--patch <文件>应用配置覆盖——最后一层、优先级最高的 patch
--dump-config打印当前组装后的配置
--dump-default-config不初始化 profile,打印组装出的树
--helplauncher 自己的帮助
--port透传给启动的 app,被 launcher 消费

--port 列在这里但要加注,因为它是最常被写错的那个:它属于 app,所以必须排在 launcher 的 flag 后面。

参数顺序规则

launcher 先解析自己的 flag,遇到第一个它不认识的 token,之后全部算 app 的参数序列。

dsh --profile web --port 3100      # 正确

--port 之前的都是 launcher 的词汇,--port 3100 交给 web app。反过来写,launcher 会在 --port 处停止解析,于是 --profile web 被当成 app 不认识的参数丢过去。

在脚本里这比交互式更要命,因为变量展开为空会让那条分界线整体位移。把变量加引号。

退出码

无效命令、属于别的模式的选项、配置错误和启动失败,一律非零退出

这个保证比听上去更强。自动化 agent 里你真正怕的不是崩溃,而是「悄悄成功但什么都没干」。一个放错位置 的 flag 会让 launcher 大声失败,意味着坏掉的流水线在第一次运行就暴露,而不是第十次。

if ! dsh --profile headless "$JOB"; then
  echo "harness 执行失败" >&2
  exit 1
fi

路径与环境变量

路径内容
$DSH_HOMEharness home 目录,你所有覆盖的根
$DSH_HOME/profiles/<名字>一个 profile:bundle 列表、out-of-tree 插件、patch 文件
$DSH_HOME/profiles/<名字>/cordis.patch.ymlprofile 级 patch 层
$DSH_HOME/cordis.patch.ymlhome 级 patch 层——对所有 profile 生效,且压过 profile 自己那份
$DSH_HOME/settings.yaml自定义服务商与模型覆盖
$DSH_HOME/.credentials.yaml凭证,单独存放,好让 settings.yaml 可以共享

检视组装结果

dsh --profile web --dump-config

打印四层全部应用后、每一行可被 patch 命中的配置——bundle、profile patch 文件、home patch 文件、 --patch。行为和预期打架时,以这份输出为准,而不是你的源文件。

dsh --profile web --dump-default-config

不初始化 profile,打印组装出的树。两者 diff 是看清自己改了什么最快的办法。

管理插件

dsh plugin --profile web add some-cordis-plugin
dsh plugin --profile web remove some-cordis-plugin

子命令把参数转发给 pnpm,所以 pnpm 全套词汇可用,含版本号。插件装进 profile 自己的 node_modules——这也是两个 profile 能各持互不兼容插件集而互不干扰的原因。

bundle 的解析顺序:先 dsh 安装目录,再 profile 的 node_modules

速查

# 交互式浏览器 agent
dsh web
 
# 同一件事,显式写法,换端口
dsh --profile web --port 3100
 
# CI 用的单次执行
dsh --profile headless "总结失败的测试"
 
# 我到底在跑什么?
dsh --profile web --dump-config
 
# 干净 profile 长什么样?
dsh --profile web --dump-default-config
 
# 不动任何文件试一个改动
dsh --profile web --patch ./experiments/strict-approval.yml
 
# 只给一个 profile 装插件
dsh plugin --profile web add some-cordis-plugin

常见问题

为什么 flag 的顺序有影响?

launcher 先解析自己的 flag,遇到第一个不认识的 token 就把后面全部当作 app 的参数。所以 --profile 这类 launcher flag 必须排在 --port 这类 app flag 前面。

--dump-config 和 --dump-default-config 有什么区别?

--dump-config 显示当前配置状态,即你的 patch 层生效之后的结果。--dump-default-config 不初始化 profile,显示干净基线。两者 diff 一下就是你改了什么。

dsh 失败会非零退出吗?

会。无效命令、属于别的模式的选项、配置错误和启动失败,一律非零退出——这正是它能安全地被脚本包起来的原因。

接着看