English | 简体中文
@zhangzhian/dsh-devtools-settings
dsh-devtools 的设置外壳:设置左栏里的一条「开发工具」导航项。点进去是一个
控制台首页(一句话介绍 + 版本与更新 + 功能插件卡片),点一张卡片就下钻到那个
插件的设置页 —— 像跳一个路由,而不是在一个常驻菜单里切页签。
它自己只有控制台这一页 —— 其余页面正文由其它组件包注册进 devtools.settings.tab。
安装
dsh plugin add @zhangzhian/dsh-devtools
@zhangzhian/dsh-devtools 是清单包,装它会带齐所有组件(包括这个)。只想装外壳:
dsh plugin add @zhangzhian/dsh-devtools-settings
提供什么
- 设置左栏的一条「开发工具」导航项(
settings.section,id: devtools,order: 30),带自己的图标。 - 控制台首页(
devtools-home,order: 10,默认打开的第一页):标题 + 一句介绍 + 配套软件入口 + 「版本与更新」卡(当前版本 / 最新版本 / 升级 / 右上角刷新)+「功能 插件」卡片网格。卡片跟着账本自动长:任何包往devtools.settings.tab注册页面, 控制台就多一张卡。 - 配套软件入口(控制台里一条 63px 横条):在「版本与更新」卡上面、介绍之后 ——
左侧「DSH Remote / 专属配套软件」+ 三个卖点,右侧浅蓝胶囊「官网下载」(悬停转实心品牌
蓝,键盘聚焦有 focus 环)。整条是一个
<a>(官网下载、target="_blank"、rel="noopener noreferrer"),position: sticky; top: 0:默认普通 一条,超一屏滚动时钉在顶部。 - 下钻视图:点一张卡切到那一页;页头是「返回控制台 + 图标 + 名字 + 一句话说明」,正文
全宽,返回控制台按钮和 Esc 都能回来。页头吸顶 —— 正文往下滚时钉在顶部不动
(滚动容器是平台设置弹层,页头整块 sticky、底色不透明)。
页头高度写成自定义属性
--dsh-devtools-page-bar-h(91px)并用min-height钉住:正文里 也有要吸顶的东西(远程接入的两个 tab),得知道从哪儿开始吸。 - 版本与更新(真能升级):宿主半身提供
dshDevtoolsUpdateRemote 服务 (status/check/upgrade)。自动检查默认执行(打开卡片时若从没查过 / 距上次 超过 6 小时就查一次),面板上没有开关。判据只有一条:npm registry 上的 latest 比现在 装的更新吗;动作也只有一种:在 profile 目录里pnpm add @zhangzhian/dsh-devtools@<版本> --config.minimum-release-age=0(回读版本一致 才算成功,见src/update-service.ts顶部)。link:安装同样按 npm 判据、装 npm 发布版,只多一句提醒(升级会把依赖换成发布版)。所有包锁步同版本,所以"当前版本"读 自己那份package.json就等于读聚合包。 - 子槽
devtools.settings.tab:其它包把设置页注册进来,就会出现在控制台的卡片网格里 ({ kind: 'list', scope: 'root' })。 - 「dsh 管理终端」:宿主半身在
$DSH_HOME/devtools/bin/下生成 wrapper (dsh-shell)+ 环境文件 + rc 垫片,清单 patch 把它追加进终端控制器的shellCandidates—— 新建终端的 shell 下拉里多一项dsh-shell。选了它的终端里就有 dsh 的运行环境:DSH_HOME/DSH_PROFILE/DSH_PROFILE_DIR,以及和宿主 同一套的dsh/pnpm/node(PATH 前缀),可以在里面dsh plugin add/remove、升级 dsh、跑pnpm。
它是自家配套软件的固定入口,不是广告位。 DSH Remote 是这个插件的专属配套软件:没有
「推广」字样、没有第三方免责声明;不来自页签账本、不随插件增减变化、不参与下钻;地址写死在
PROMO_URL,文案在 locale 的 7 个 promo.* 键。
为什么需要这个终端。 平台的终端起 PTY 时只传 { DSH_SESSION_ID },而子进程环境是
scrubbedParentEnv() 的产物 —— 所有 DSH_* 都被剥掉(平台原话:"harness identity
never leaks implicitly")。所以不选这一项时,在终端里敲 dsh plugin --profile web add ...
会去操作 ~/.dsh,而不是这个 dsh 真正用的 home/profile。模型用的 bash 工具走平台的受信任
注册表,终端这条链路没接它。
它影响不到宿主机。 环境变量只是"起这个终端时"传给子进程的一份 map:宿主
process.env 不被写回,别的终端拿不到,也没有任何文件级环境(/etc/profile、
~/.zshrc)被修改。rc 垫片解决反方向的坑:交互式 shell 会先 source 你自己的
~/.zshrc / ~/.bashrc(提示符、补全、别名照旧),垫片再 source 环境文件、把 PATH
前缀强制挪回最前 —— ⚠️ 只做"缺了才补"不够:rc 里常见的
export PATH="$HOME/xxx/bin:$PATH" 不删前缀、只把它挤到后面,node / pnpm 就变成你
自己那套了(真机上发现的)。前缀不会重复出现。
三个已知边界。 ① wrapper 名字不是 bash/zsh,平台的活动探测
(prepareShellActivity)不生效:它不会被"未看管 72 小时"自动清理误杀,代价只是
空闲判定不准。② wrapper 不存在时下拉里静默少一项(平台把"可执行文件不存在"当跳过),
所以 dshShell: false 或宿主半身没起来时不报错。③ Windows 上不生成(wrapper 是 POSIX
sh 脚本)。
配置(这一行的 config: 段):dshShell(默认 true,关掉就不生成、下拉里也没了)、
shell(留空 = 按 zsh → bash → sh 探测)、inheritShellRc(默认 true;设 false
就是 zsh -f -i / bash --noprofile --norc -i,环境确定但没有你的补全与别名)、env(额外
注入的变量,保留键被丢弃)、pathExtra(追加进 PATH 前缀的目录)。不想让下拉出现这一项,
也可以在 profile 的 cordis.patch.yml 里写一条同 id 的 terminal-controller 行把
shellCandidates 盖回默认值。
交互与无障碍。 控制台的插件菜单是一组按钮(整卡可点,悬停/聚焦有反馈);下钻页是
role="region" + aria-label。不再是 role="tablist":两屏互斥、点卡片就是"跳转",
硬套标签页语义会念出屏幕上不存在的控件(见 src/client/index.tsx)。焦点跟着路由:下钻进新页面、返回回到那张卡;只挂载
访问过的页(keep-alive)。
导航图标是怎么换上的。 平台的设置导航按 section id 硬编码图标,未知 id 一律给齿轮,
而 settings.section 只有 id / order / label —— 没有"贡献图标"的槽。所以本包不改
平台界面:给自己的那一行打 data-dsh-devtools-nav,用本包 CSS 藏掉平台 SVG、::before +
CSS mask 画自己的图标(形状取自 IconApiOutlineMedium,颜色跟导航行文字走,选中/悬停变主
色)。整段包在 @supports 里:不支持 mask 的浏览器保留齿轮而非空白。平台哪天长出图标槽,
这段可以删掉。
要求
- DSH
0.2.0-rc.2或更新。 - 没有别的依赖。没有贡献方时控制台显示空态。
- 「dsh 管理终端」需要 POSIX 系统(macOS / Linux)和带终端控制器的 profile
(web 形态)。它靠清单 patch 改
terminal-controller那一行,所以本 bundle 必须排在@deepseek-ai/dsh-web-app之后(dsh plugin add的追加顺序就是如此)。
English
@zhangzhian/dsh-devtools-settings
The settings shell of dsh-devtools: one "Dev tools" entry in the Settings
sidebar. It opens a console home (one-line intro, version & updates, plugin
cards); clicking a card drills into that plugin's settings page — a route, not
tabs in a permanent menu.
The shell ships only that console page — every other page body is registered into
devtools.settings.tab by the component packages.
Install
dsh plugin add @zhangzhian/dsh-devtools
@zhangzhian/dsh-devtools is the manifest package; installing it pulls in every
component, including this one. To install the shell alone:
dsh plugin add @zhangzhian/dsh-devtools-settings
What it provides
- One "Dev tools" entry in the Settings sidebar (
settings.section,id: devtools,order: 30), with its own icon. - A console home page (
devtools-home,order: 10, the first page you land on): heading + one-line intro + the companion-app bar + a "Version & updates" card (installed / latest / upgrade / refresh) + a "Plugins" card grid. The grid grows by itself: any package registering intodevtools.settings.tabgets a card. - A companion-app bar (63px, inside the console): right above the "Version &
updates" card, after the intro — "DSH Remote / Companion app" plus the three selling
points on the left, a pale-blue "Official download" pill on the right (solid brand
blue on hover, focus ring on keyboard focus). The strip is one
<a>(download,target="_blank",rel="noopener noreferrer"),position: sticky; top: 0— plain by default, pinned to the top once the content scrolls. - A drill-down view: clicking a card opens that page; its header is
"back to console + icon + name + one-line description", body at full width. Both
the back to console button and Esc return you. The header is sticky —
pinned while the body scrolls (the scroller is the platform's settings pane; the
header sits in one
position: stickywrapper with an opaque background). Its height is published as--dsh-devtools-page-bar-h(91px) and pinned withmin-height: the body has sticky things of its own (the two remote-access tabs) and must know where the header ends. - The
devtools.settings.tabchild slot: any package registering a page into it appears in the console's grid ({ kind: 'list', scope: 'root' }). - A "dsh management terminal": the host half generates a wrapper (
dsh-shell) plus an env file and rc shims under$DSH_HOME/devtools/bin/, and the bundle patch appends its path to the controller'sshellCandidates— so the shell dropdown gets one more entry,dsh-shell. The one terminal you pick it for carries the dsh runtime environment:DSH_HOME/DSH_PROFILE/DSH_PROFILE_DIRand the samedsh/pnpm/nodethe host runs with (PATH prefix) — where you candsh plugin add/remove, upgrade dsh or runpnpm.
A fixed entry to our own companion app, not an ad slot. DSH Remote is this
plugin's dedicated companion app — no "Promoted" wording, no third-party
disclaimer. It never comes from the tab ledger and takes no part in the drill-down;
the URL is hardcoded in PROMO_URL, the copy lives in seven promo.* keys.
Why it is needed. The platform spawns a terminal PTY with { DSH_SESSION_ID }
only, and the child environment comes from scrubbedParentEnv() — every DSH_*
variable is stripped ("harness identity never leaks implicitly"). Without this
entry, dsh plugin --profile web add ... in a terminal operates on ~/.dsh, not
the home/profile this dsh uses. The model's bash tool uses the platform's trusted
registry; this terminal path was never wired to it.
It cannot affect the host. Env vars are a per-child map for that terminal: the
host's process.env is never written back, other terminals do not see it, and no
file-level environment (/etc/profile, ~/.zshrc, …) is modified. The rc shims
solve the opposite problem: an interactive shell sources your ~/.zshrc /
~/.bashrc first, then the shim sources the environment file and forces the PATH
prefix back to the front — ⚠️ "prepend only if missing" is not enough: a common
export PATH="$HOME/xxx/bin:$PATH" does not delete the prefix, it pushes it back,
and node / pnpm there become yours instead of the host's (found on a real
machine). The prefix never appears twice.
Three known edges. (1) The wrapper is not named bash/zsh, so the platform's
activity probe (prepareShellActivity) does not apply: it is never reaped by the
"unattended for 72 hours" rule, only idle state gets imprecise. (2) When the wrapper
is missing, the dropdown silently loses that entry (the platform treats "executable
not found" as a skip), so dshShell: false or a failed host half is never an error.
(3) Nothing is generated on Windows (the wrapper is a POSIX sh script).
Configuration (this row's config:): dshShell (default true; off = no
generation, no dropdown entry), shell (empty = probe zsh → bash → sh),
inheritShellRc (default true; false = zsh -f -i / bash --noprofile --norc -i, deterministic but without your completion and aliases), env (extra
variables; reserved keys dropped) and pathExtra (extra PATH-prefix dirs). To keep
it out of the dropdown, add a terminal-controller row with the same id in the
profile's cordis.patch.yml restoring platform defaults.
- Version & updates (actually upgrades): the host half exposes a
dshDevtoolsUpdateremote service (status/check/upgrade). Auto-check runs by default (on open, if it never checked or the last check is over 6 hours old); there is no toggle in the panel. One rule: is the npm registry latest newer than what is installed? — one action:pnpm add @zhangzhian/dsh-devtools@<version> --config.minimum-release-age=0in the profile, then read the installed version back; only a match counts (see the top ofsrc/update-service.ts). Alink:install follows the same rule and installs the npm release too, with one note (upgrading replaces thelink:dependency with the published package). Every package here shares one version, so its ownpackage.jsonequals the aggregate package. On failure the panel shows the failing step's raw output.
Interaction and accessibility. The console's plugin menu is a set of
buttons (whole card clickable, with hover/focus feedback); the drill-down page
is a role="region" with an aria-label. It is no longer a role="tablist":
the screens are mutually exclusive and clicking a card is navigation, so tab
semantics would announce a control that is not on screen (see
src/client/index.tsx). Focus follows the route: drilling in moves focus into the
new page, returning puts it back on the original card; only visited pages stay
mounted (keep-alive).
How the navigation icon gets there. The platform hardcodes nav icons by section
id and falls back to a gear for unknown ids, while settings.section only accepts
id / order / label — no icon slot. So this package never touches the platform
UI: it marks its row with data-dsh-devtools-nav, hides the platform SVG via CSS and
paints its icon with ::before plus a CSS mask (shape from IconApiOutlineMedium,
colour riding the row's text colour). If the platform grows an icon slot, this block
can be deleted.
Requirements
- DSH
0.2.0-rc.2or newer. - Nothing else. With no contributors the console shows its empty state.
- The "dsh management terminal" needs a POSIX system (macOS / Linux) and a profile
with the terminal controller (the web shape). It patches the
terminal-controllerrow through the bundle patch, so this bundle must come after@deepseek-ai/dsh-web-app— asdsh plugin addappending it does.