dsh-useful-tools · DSH 有用小工具箱
小工具集合插件。mark_read 让指定命令(cat/grep/head/sed/awk…)读过的文件自动算已读,
解决官方原生 write/edit 的「cannot modify "…": file has not been read」门禁误伤
(内容明明已经用 grep/脚本等方式看过,原生写仍要求先用 read 工具读一遍)。
功能
| 工具 | 参数 | 简介 |
|---|
mark_read | commands undo | 配置读命令集合 + 自动标记钩子(两者是同一功能)。commands 写命令串 / 传文件路径 / 不传用预置清单(lib/core/mark-read/read-commands.json);undo:true 撤销。两种读法都算:① 宿主工具(如 grep 工具);② bash 里跑的命令(cat a.txt、grep x b.txt)。跨平台别名归一:Get-Content/gc/type/more → cat,Select-String/sls/findstr → grep。集合里的命令执行后,钩子(tools/post-execute)自动把它读过的所有文件补发观测,不用指定文件;配置落盘 <DSH_HOME>/useful-tools/config.json、每次现读 ⇒ 配置一次永久有效 |
quick_command | action name command cwd description timeoutMs | 快捷命令:执行/管理 <DSH_HOME>/useful-tools/quick-commands.json 里定义的命令。action=run(缺省,按 name 执行;同时传 command 会先持久化再执行,等效 add 后执行)/ add / edit / delete / list(列出所有名字+命令)。cwd 指定执行目录(缺省空 = 宿主进程 cwd)。无内置预置:定义完全来自该文件,首次是空表;重启 DSH 这类命令由使用方自己 add |
sync_plugin | write profile source dshHome pluginName register | 把插件工作区代码同步到 profile 的 local-plugins。缺省 dryRun,write:true 真写;register:true 写 profile 的 dependencies + bundles 并注册;dshHome 缺省当前实例,指向测试环境即可装进去。写盘后自动:权限/属主对齐(目录 755 / 文件 644,属主对齐 profile)+ 读权限自检 + 依赖体检(file: 路径失效会明确报出) |
minihost | action plugin port path method body services env force | 后端模拟:本地 mini 宿主,把插件服务端代码跑起来。action=start(启动)/ stop(停止)/ status(状态)/ call(请求);services 可注入宿主服务(插件依赖 storageDomain/sessions 等真实服务时用)。默认隔离 DSH_HOME(写到 .minihost/home-<port>,不碰真实会话/附件库),需要贴近真实环境时用 env: { DSH_HOME: "<真实 .dsh>" } 覆盖。内置 webServer/tools/storageDomain/sessions 宽容桩;force:true 在已有实例时按新参数重启;404 会带原因提示 |
preview | action plugin spec out file port backend host lan | 前端模拟:插件预览页。action=gen(生成,preview.spec.mjs 可选——不写走通用自动挂载,标题默认取插件目录名;产物在插件内且未忽略时会提示入库风险)/ serve(启动,backend 可省略——自动沿用最近一次 minihost 实例并返回 backendSource;也可显式指真实 DSH 后端;host=0.0.0.0 或 lan:true 供局域网)/ stop / status。页面顶部内置错误面板(运行期错误直接可见) |
verify_plugin | plugin source profile log ui | 验证任意 DSH 插件是否装载成功(只静态分析,不执行插件代码):入口/apply/注册调用/effect 形态 + inject 服务名校验(拼错给近似建议)+ 激活诊断(dependencies / bundles / loader 行 / did not activate 归类)+ 依赖解析 + 客户端入口与 UI 骨架(controls=0 直接判失败)+ HMR 覆盖判定 + DSH 插件规则(9 条,与 dsh-git-push 审计同步维护) + 部署一致性 + 宿主日志。plugin 传插件名或根目录,缺省巡检所有已装插件 |
test_env | action kind version root force plugin pluginName dir dshBin | 测试环境(隔离实例):action=create(创建)/ start(启动)/ stop(停止)/ status(状态)。独立 DSH_HOME + 自动端口,随便崩不碰主实例。dshBin 可传 shell wrapper(自动解析到真实 bin.js);启动超时会直接带回日志里的关键错误行 |
useful_tools | action tool | 本插件工具清单(动态扫描 lib/shared,不维护硬编码列表):action=help(缺省,输出每个工具的名字/说明/参数/示例调用)/ list(紧凑清单);tool 可选,只看某一个工具 |
ui_check | plugin file out write limit | UI 静态骨架检查:解析插件 lib/client.js 的 React 元素调用,产出「代码里声明了哪些控件」的 HTML 骨架页 + JSON 清单(插槽注册 / 层级 / className / 内联样式 / 来源行号)。write:false 只解析不写盘;缺省输出 <plugin>/assets/ui-skeleton.html + 同名 .json。静态解析,不代表运行时渲染结果 |
用法
mark_read commands: "cat,grep,head" # 加入读命令集合(持久生效)
mark_read # 不传 commands = 用预置清单(20 个常见读文件命令)
mark_read commands: "<命令清单文件路径>" # 从文件读命令集合(每行/逗号一个)
mark_read commands: "grep" undo: true # 撤销 grep(从集合移除)
verify_plugin plugin: "dsh-git-push" # 验单个插件(也可传插件根目录)
verify_plugin # 巡检所有已装插件
quick_command restart # 执行预置 restart(重启 DSH,当前连接会断)
quick_command list # 列出所有快捷名 + 命令
quick_command add name: "logs" command: "tail -n 50 <日志路径>" # 新增定义(落盘)
quick_command run name: "logs" command: "tail -n 20 <日志路径>" # 先持久化(新增或更新)再执行
quick_command edit name: "logs" command: "..." # 修改已定义命令
quick_command delete name: "logs" # 删除定义
quick_command run name: "logs" cwd: "/var/log" # 指定执行目录(只对本次生效)
useful_tools # 列出本插件所有工具(名字/说明/参数/示例)
useful_tools action: "list" # 紧凑清单
useful_tools tool: "quick_command" # 只看一个工具
mark_read 的语义是配置「哪些命令算读」,不是逐个标记文件:集合里的命令执行后,
它读过的所有文件自动补发观测(tools/post-execute 钩子),不需要指定文件——要指定文件直接 read 就行。
集合落盘到 <DSH_HOME>/useful-tools/config.json,钩子每次现读,所以配置一次、永久有效(改完即时生效、重启仍在)。
verify_plugin 给三项证据,专治「装完插件发现工具没出现」:
- 装配结构(静态读源码):入口是否存在、是否导出
apply、是否有 tools 注册调用、
apply 顶层是否 return {(cordis 会判 Invalid effect 并卸载整条 fiber,工具全部失效)、
cordis.patch.yml 是否有顶层 - insert:。入口用 export * 转出的会跟进去看。
- 部署一致性:源码根 vs profile 的 local-plugins/node_modules 副本逐文件比内容,
不一致说明宿主跑的不是你改的代码。
- 宿主日志:扫
did not activate / TypeError 等行;日志早于源码最后修改时间的
判为修复前的历史记录,降级为提示。
只读文件、不 import 被验证插件——插件模块顶层和 apply 都可能带副作用,验证不该把它跑起来。
代价是「注册是否真的成功」仍需宿主内实测(工具是否真的出现在工具清单里),输出里的 staticOnly/notes 会标注这一点。
前后端模拟链路
minihost 是后端模拟,preview 是前端模拟,两者通过 serve 的 backend 参数串成一条链,
全程不碰真实后端:
minihost { action: "start", plugin: "<插件根>", port: 31177 } # ① 起后端(插件服务端代码)
preview { action: "gen", plugin: "<插件根>" } # ② 生成前端预览页(无 spec 时自动挂载插槽组件)
preview { action: "serve", file: "<插件根>/assets/preview.html", backend: "http://<本机IP>:31177" }
# ③ 起前端,/api/* 打到 ①
minihost { action: "status" } · preview { action: "status" } # 查状态
minihost { action: "stop", port: 31177 } · preview { action: "stop", port: 31188 }
minihost 的两个细节
- 路由匹配只看路径:
/api/xxx?fromIndex=true 按 /api/xxx 匹配,query 留给 handler 自己解析
(旧实现拿含 query 的完整 URL 比对,任何带参数的接口都会 404)。
- 宿主服务注入:插件接线依赖真实服务(
storageDomain / sessions / workspaceRegistry 等)时,
传 services 指向一个模块——该模块默认导出一个「服务对象」或「返回服务对象的函数」,注入项覆盖同名默认服务:
minihost { action: "start", plugin: "<插件根>", port: 31177, services: "<插件根>/test/api/helpers/fake-services.mjs" }
backend 也可以指向真实 DSH 后端(如 http://<本机IP>:30800),此时是「前端模拟 + 真实后端」。
测试环境(隔离实例)
起一个与主实例完全隔离的 DSH:独立 DSH_HOME、独立端口(3083-3182),里面随便装插件、随便改、随便崩。
test_env { action: "status" } # 看两个环境(test/clean)是否存在/运行
test_env { action: "create", plugin: "<插件根>" } # 创建 <工作区>/<版本>@Save-test 并把插件装进去
test_env { action: "start", plugin: "<插件根>" } # 创建(若无)并启动,返回端口
test_env { action: "stop" } # 停止(按端口记录找进程 TERM)
plugin 可选:给了就把该插件复制进 profiles/web/node_modules 并注册到 bundle 列表;不给就是纯裸环境。
环境根优先级:参数 root > 环境变量 DSH_TEST_ENV_ROOT > 插件配置 <DSH_HOME>/useful-tools/config.json 的 testEnvRoot > 推算(DSH_WORKSPACE / DSH_HOME 上一级「工作区」)。不写死路径,部署方按配置决定位置。
配合 sync_plugin 可以把插件同步 + 注册进这个隔离环境(dshHome 指向环境目录即可,缺省是当前实例):
sync_plugin { write: true, register: true, dshHome: "<环境目录>" }
快捷命令(quick_command)
定义存 <DSH_HOME>/useful-tools/quick-commands.json,形如:
{
"restart": {
"command": "nohup sh -c 'sleep 2; \"/path/to/start.sh\" restart' >/dev/null 2>&1 &",
"description": "重启 DSH 主实例"
},
"logs": { "command": "tail -n 50 /path/to/dsh.log", "description": "看最近日志" }
}
- 无内置预置:文件不存在时是空表(
list 返回 count:0 并提示用 add),定义完全由使用方创建。
例如本机重启 DSH:
add name:"restart" command:"nohup sh -c 'sleep 2; /var/packages/DeepSeekHarness-NAS/target/start.sh restart' >/dev/null 2>&1 &"
(后台化以免重启杀掉进程后拿不到结果;该形态仅 Linux/macOS,Windows 换对应重启命令)。
run 只执行 JSON 里已定义的命令;同时传 command 时先写入 JSON(新增或更新)再执行,不是临时执行。
cwd 指定执行目录:缺省空 = 沿用宿主进程的 cwd(不一定是当前工作区);
传了会校验「存在且是目录」,并解析成绝对路径。add/edit/run 带 command 时随定义落盘,
run 单独传 cwd 只对本次执行生效、不改已有定义。
- 执行走
bash -c(Windows cmd /c),默认超时 60 秒,stdout/stderr 各截断 20000 字符。
UI 静态骨架(ui_check)
解析插件的 lib/client.js,把「代码里声明了哪些控件」变成可看、可 diff 的产物:
ui_check plugin: "<插件根>" # 产出 assets/ui-skeleton.html + .json
ui_check plugin: "<插件根>" write: false # 只解析不写盘
ui_check plugin: "<插件根>" file: "lib/ui.js" out: "/tmp/ui.html"
产物两份:
ui-skeleton.html:插槽注册表 + 结构树 + 静态还原预览 + 控件清单,浏览器打开即看
ui-skeleton.json:同一份数据的机器可读版本,控件带 visible: null——留给将来的渲染探针填 true/false
它回答什么、不回答什么(这个边界决定它不能被误用):
| 问题 | 能否回答 |
|---|
| 代码里声明了哪些控件、层级、className、文本 | ✅ 能,带来源行号 |
| 哪个插槽注册了、渲染的是哪个组件 | ✅ 能(组件在文件内定义时还会展开该组件的 UI) |
| 条件分支 / 逻辑与里的元素 | ✅ 能(收进 conditional 子树,不漏报) |
| 运行时到底渲染出来没有(TDZ、条件为 false、hooks 报错、map 空数组) | ❌ 不能 |
| 真实外观(主题 CSS 变量、宿主布局约束) | ❌ 不能,只能用内联样式近似 |
所以它是静态骨架,不是渲染检查:判断「实际渲染出来没有」仍然需要在真实宿主里看。
将来补渲染探针时,两者共用同一份清单格式(visible 字段),可直接 diff 出「声明了但没渲染」的控件。
静默失败诊断(verify_plugin 的六段)
插件开发里最难查的不是报错,是不报错。verify_plugin 把这类问题逐段列出来:
| 症状 | 检查段 | 判定依据 |
|---|
| 装了但不激活、apply 没执行 | activation | profile 的 dependencies / dsh.profile.bundles / 离线组合出的 loader 行,缺哪项报哪项 |
| 服务拿不到、路由 404 | inject | 声明的服务名与当前 DSH 安装里实际存在的比对(super(ctx,'x') / inject([...]) / .get('x')),拼错给近似建议 |
| import 失败、依赖找不到 | deps | 在目标 profile 的 node_modules 与 Node 解析里逐个试;file: 路径失效单独报 |
| UI 空白、控件没出来 | client + ui | 有 dsh.client 就必须有 lib/client.js 且调用 __ModuleLoader__.load;UI 骨架 controls=0 或插槽 0 节点直接判失败 |
| 改了代码没生效 | hmr | 读 profile 的 hmr 配置,判断插件路径是否在 watch 范围 |
| 同步后宿主读不到 | sync_plugin 的 permissions / readable | 写盘后对齐 755/644 与属主,再逐个 access(R_OK) |
边界:激活诊断是离线组合(profile 的 patch + 各 bundle 的 patch),不启动宿主;运行期真实装配仍以宿主日志为准。
DSH 插件规则(与 dsh-git-push 审计同步维护)
同一套规则有两份独立实现,判定口径相同:
| 位置 | 形态 | 用途 |
|---|
dsh-git-push 的 lib/audit-rules/audit-rules-dsh.yml | 审计规则(提交时拦截) | 逐行扫描改动 |
本插件 lib/core/verify-plugin/dsh-rules.mjs | 工具检查(verify_plugin) | 整仓扫描,并与激活/依赖/UI 检查合并出结论 |
维护约定:两个插件不互相 import,各自实现;规则增删改必须两边同步,语义保持一致。
| 规则 | 严重度 | 判定 |
|---|
dsh/import-bare-cordis | error | import … from 'cordis' / require('cordis') |
dsh/import-bare-schemastery | error | 裸名 schemastery |
dsh/tool-render-empty-body | error | render(…) {} 或 return null/undefined |
dsh/client-node-builtin-require | error | client 半部 require Node 内置模块 |
dsh/schema-secret-field-role | warning | token/secret/apiKey 字段缺 .role('secret') |
dsh/patch-insert-unique-id | warning | insert 行 id 与按 id 覆盖行重复 |
dsh/client-module-loader-id | warning | client 入口未走 __ModuleLoader__ |
dsh/ctx-undeclared-property-read | blocker | 读 ctx.sessionId 等未注入属性(注释行豁免) |
dsh/host-api-guessed-fallback-chain | error | addRoute || route 这类猜测 API 兜底链 |
配置(<DSH_HOME>/useful-tools/config.json)
{
"observeHook": true,
"observeTools": ["cat", "grep", "head", "sed", "awk"],
"testEnvRoot": "<可选,test_env 的环境根>"
}
observeHook:自动标记钩子总开关(默认开)
observeTools:读命令集合——填什么、什么就生效;钩子每次现读,改动即时生效无需重启
- 也可以直接用
mark_read 工具改这个集合(commands 加入 / undo 撤销)
- 预置清单:
lib/core/mark-read/read-commands.json(20 个常见读文件命令)
语义与边界(实测结论)
- 观测有效到文件版本再变为止:版本不变一直放行;文件被任何一方改动后,
原生写改为报
file changed since it was read — re-read the file, then retry,重新标记即可。
- 官方 read 带 offset/limit 只看几行同样算读过 ⇒ 把 grep 纳入与宿主自身尺度一致,不算放权。
- bash 里跑的命令也算读(0.1.13 起):钩子解析命令串(
cat a.txt、grep x b.txt),
命令名在集合里就标记其文件参数;跨平台别名归一(Get-Content/gc/type → cat,Select-String/sls/findstr → grep)。
已知边界:xargs、$(...)、for 循环里的文件参数提取不到(启发式解析)。
- 单次钩子调用最多标记 40 个文件(防全仓 grep 观测风暴)。
安装
DSH 0.2.1-alpha.2+。装好后需重启 DSH 生效(工具与钩子在启动时装配)。
本插件只注册工具,不提供斜杠指令(原因与恢复条件见契约 C-204)。
文档
| 文件 | 内容 |
|---|
README.md | 门面:简介、工具表、用法、配置、安装、目录结构、版本记录 |
docs/CONTRACT.md | 契约:命名 / 装配 / 部署 / 依赖 / 验证 / 测试 / 版本的硬约束,实现与验收的唯一依据 |
docs/NOT-DONE.md | 未实现能力与原因(预览页截图 / 自包含快照 / shell 启动器),含未来若做的形态 |
目录结构(目录即工具名 · 一个文件夹一个工具)
lib/
├── index.js 装配入口:扫描 lib/shared/* 动态注册(工具名取文件夹名)
├── shared/ 工具适配层 —— 新增工具只需新建一个文件夹,不必改装配
│ ├── mark_read/index.js → 工具 mark_read
│ ├── sync_plugin/index.js → 工具 sync_plugin
│ ├── minihost/index.js → 工具 minihost (action=start|call|stop|list|status)
│ ├── preview/index.js → 工具 preview (action=gen|serve|stop|list|status)
│ ├── verify_plugin/index.js → 工具 verify_plugin(验证任意插件的装载与工具注册,只静态分析)
│ └── test_env/index.js → 工具 test_env(测试环境:create/start/stop/status)
└── core/ 共享实现(不含注册逻辑)
├── mark-read/ 观测补发 + 台账 + 自动标记钩子
├── plugin-sync/ 同步实现(CLI 同款)+ profile 探测 + 原子拷贝 + 注册
├── minihost/ 主进程 host.mjs + 每请求子进程 host-child.mjs
├── preview/ DSH 根/UMD 自探测 + 单文件 HTML 壳 + 静态服务(含 /api 反代)
├── verify-plugin/ 静态分析(入口/apply/注册调用)+ 部署一致性 + 宿主日志
└── test-env/ 隔离实例:环境骨架 / 启停 / 端口探测 / 状态
设计契约:命名、装配、部署、依赖、验证、测试、版本等硬约束见 docs/CONTRACT.md
(实现与验收的唯一依据)。新增工具只需新建 lib/shared/<名字>/index.js 并导出 { description, parameters, run }。
预览页(统一管理 · 主题插件除外)
通用件(本插件) lib/core/preview/:DSH 根与 DSH_HOME 自探测 · React UMD 缓存 · 单文件 HTML 壳
· 静态服务 + /api 反代 · 进程台账(.preview/instances.json)· 通用自动挂载垫片
插件自持 assets/preview.spec.mjs(可选增强:自定义垫片 harness / 假数据 / 首屏注入 post / 输出路径)
└ 默认字段:{ title, css, harness, body, post, client, output }
主题插件 允许完全自持预览(皮肤注入与真实渲染强耦合,不走本工具)
# 工具(一个 preview 工具,动作走 action 参数)
preview { action: "gen", plugin: "<插件根>", spec?, out? } # 生成单文件 preview.html(无 spec 时自动挂载插槽组件)
preview { action: "serve", file: "<preview.html>", port?, backend? } # 起服务;给 backend 则 /api/* 反代真实后端
preview { action: "stop", port | file }
preview { action: "status" } # 存活实例(等价 list)
实测(2026-10-17):dsh-session-conductor 无 spec → gen 1.28MB → serve 200 → #root 渲染出真实界面(5 个插槽、0 错误);
dsh-skill-scoreboard 修复 TDZ 后同样渲染成功。
版本记录
| 版本 | 变更 |
|---|
| 0.1.26 | 按 dsh-session-migrate 的实战反馈改 5 处:preview gen 说明写明 preview.spec.mjs 可选、产物在插件内且未忽略时提示入库风险、页面顶部内置错误面板(运行期错误可见);preview serve 的 backend 可省略(自动沿用最近一次 minihost 实例并返回 backendSource);minihost start 新增 env 参数且默认隔离 DSH_HOME(写到 .minihost/home-<port>,依赖解析用 --deps 补回真实 profile)。多挂载点 mounts[] 记入 NOT-DONE 待办。 |
| 0.1.25 | 把 dsh-git-push 的 9 条 DSH 插件规则移植到 verify_plugin(独立实现,两个插件不互相 import):裸名 cordis/schemastery、render 空实现、client 引 Node 内置、凭据字段缺 role('secret')、patch insert id 冲突、client 未走 __ModuleLoader__、ctx 未注入属性(blocker)、猜宿主 API 兜底链。另修激活诊断的 profile 选择:显式指定 > web > 第一个(原先取 profiles[0] 会误判到历史 profile)。 |
| 0.1.24 | 按 sub2api 插件开发的实战问题清单增强「静默失败」诊断:verify_plugin 新增 inject 服务名校验(拼错给近似建议)、激活诊断(dependencies / bundles / loader 行 / did not activate 归类)、依赖解析、客户端入口与 UI 骨架串联、HMR 覆盖判定;sync_plugin 写盘后自动对齐权限/属主并做读权限自检 + 依赖体检;minihost 内置 storageDomain/sessions 宽容桩、force 重启、404 原因提示;test_env 支持 shell wrapper 形式的 dshBin 并带出超时日志关键行;ui_check 增加一行摘要;useful_tools help 增加「症状 → 工具」索引。 |
| 0.1.23 | 新增 ui_check 工具:静态解析插件 lib/client.js,产出 HTML 骨架页 + JSON 控件清单(插槽注册、层级、className、内联样式、来源行号;组件在文件内定义时展开)。支持 createElement / h / jsx / (0, ns.fn)(…) 四种写法,排除 document.createElement,条件与逻辑表达式里的元素一并收集;清单 visible: null 为将来的渲染探针预留。 |
| 0.1.22 | CI 回到固定 ubuntu-latest × Node 20/22/24:CI 是门禁,环境固定才稳定;跨平台能力(cmd /c 分支、测试不依赖 shell 内置命令)保留在代码里,但 Windows 不挂进每次提交的门禁(其 spawn cwd 与 cmd 引号语义尚未验证,需真实环境单独验证)。 |
| 0.1.21 | quick_command 去掉内置预置:定义完全来自 <DSH_HOME>/useful-tools/quick-commands.json,首次是空表(list 返回 count:0 并提示用 add),restart 这类本机命令由使用方自己 add。测试入口改为跨平台 runner node test/run-tests.mjs(自扫 *.test.mjs,不依赖 Node 20 不支持的 glob 与 Windows 不展开的通配符)。 |
| 0.1.20 | quick_command 支持 cwd 执行目录:缺省空 = 宿主进程 cwd,传了先校验「存在且是目录」并解析成绝对路径;// 带 时随定义落盘, 单独传只对本次生效。跨平台加固:单测不再依赖 shell 内置命令(用 node 打印 cwd)、测试文件统一改名 用 Node 自身展开 glob,CI 矩阵增加 。 |