🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI
广告☁️ 云服务器特惠阿里云首购 8 折 · 腾讯云合作特惠
DeepSeek Harness Hub
← 返回列表

Shadoso-w/dsh-cli-mode

DeepSeek Harnessspec-screened扫描:中风险在 GitHub 查看 ↗
未验证

DeepSeek Harness Web GUI 对话框的命令行模式。

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/19 · 已提供中文文档

DeepSeek Harness Web GUI 的命令行模式:以 ! 或 ! 开头的输入行会在会话工作区中作为真实命令运行,而不会发送给模型。

综合分
29.3
GitHub 分
29.3
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add Shadoso-w/dsh-cli-mode
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
信任档位:已验证本站已于 0 天前真实安装成功
是什么
dsh 原生插件 · tool
装得上吗
本站已真实安装成功(非静态推断)
安全吗
本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
还在维护吗
活跃:最近一次提交在 6 天前

档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →

🟢实装验证通过· 2026/9/26
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/26(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

由 DeepSeek 最新模型翻译生成
dsh-cli-mode

DeepSeek Harness Web GUI 对话框的命令行模式。
在 Web UI 对话框中以 ! 或 ! 开头时,整行作为真实命令行在会话工作目录中执行,不再发送给模型。

功能说明

| | |
| --- | --- |
| Enter | 在输入框首字符输入 ! 或 !(可带前导空格,如 ! npm --version)即进入命令行模式 |
| Red input | 命令行模式激活时,输入区文字变红(#e5484d) |
| Run | Enter 执行 · Shift+Enter 换行 · Esc 清除该行并退出命令行模式 |
| Feedback | 成功:命令已执行 +  cmd 命令已执行  + 标准输出 + cwd;失败:系统报错原文 + 「下一步建议」 编号清单 |
| No model turn | 命令不会进入模型上下文,不产生对话轮次;结果卡显示在输入区下方,可点「清除」 |

架构

dsh-cli-mode/
├── lib/
│   ├── index.js      Host 半:cordis 插件,注册 /cli-mode/* 路由 + 通过 ctx.shell 执行 + 历史持久化
│   ├── suggest.js    纯函数:标记解析、失败渲染、报错指纹 → 下一步建议
│   └── client.js     浏览器半:lazy-CJS 客户端包(无需构建),DOM 监听 + 输入区变红 + 结果卡
├── cordis.patch.yml  bundle 层:insert 一行 dsh-cli-mode
├── dsh.plugin.json   插件清单(市场/工具可读的元数据)
└── package.json      声明 dsh.bundle.patch 与 dsh.client

- Host 半是普通 ESM cordis 插件(export const name + export function apply(ctx)),只通过 ctx.get() 读取 webServer / shell / sessions / sandboxPolicy,不引入任何 @deepseek-ai/ 依赖。
- 浏览器半手写在 lazy-CJS 客户端包协议里(window.__ModuleLoader__.load({ id, factory })),因此不需要构建步骤;require 只取平台种子模块(react),与 Host 的通信使用同源 fetch 到插件自己的路由。
- 两个插槽:conversation.composer(chain,检测到标记时接管输入区)与 conversation.composer.dock(list,结果卡)。

Host 路由

| Route | Body | Response |
| --- | --- | --- |
| POST /cli-mode/exec | { command, sessionId, timeoutMs? } | { ok, kind, command?, stdout?, stderr?, truncated?, workdir, message, suggestions? } |
| POST /cli-mode/workdir | { sessionId } | { workdir } |
| POST /cli-mode/history | { sessionId } 读取 / { sessionId, entries } 写入 | { entries } |

命令历史按会话持久化在 ${DSH_HOME:-~/.dsh}/cli-mode/history/.json(最多 200 条)。

失败建议

suggest.js 按报错指纹给出可执行的下一步,覆盖:命令/别名不存在、权限不足、路径不存在、目标是目录、目标已存在、PowerShell 命令或参数无法识别、语法与引号错误、非 Git 仓库、端口占用、缺依赖模块、缺少 package.json、npm 脚本不存在、沙箱拒绝、超时,以及兜底建议。

安装到 profile

该插件是一个 bundle:profile 通过在 dsh.profile.bundles 中列出它来加载。

1. hand the package to the profile (a symlink is enough for local development)
/profiles//node_modules/dsh-cli-mode -> this directory

2. add the dependency and the bundle layer to
/profiles//package.json
"dependencies": { "dsh-cli-mode": "link:" }
"dsh": { "profile": { "bundles": [ ..., "dsh-cli-mode" ] } }

3. restart the profile so the Host mounts the row and the Web shell loads the client bundle
dsh web

可发布的安装方式与市场使用的命令相同:

dsh plugin --profile web add

dsh plugin add 会在 profile 目录中运行 pnpm,然后根据已安装状态协调
dsh.profile.bundles,因此声明了 dsh.bundle 的包会自动加入层栈。

配置
编辑每一半顶部的常量——没有设置架构,因此不可写的设置文件永远不会禁用命令行:

| 常量 | 文件 | 默认值 | 含义 |
| --- | --- | --- | --- |
| DEFAULT_TIMEOUT_MS | lib/index.js | 120000 | 每个命令的默认超时时间 |
| MAX_TIMEOUT_MS | lib/index.js | 600000 | 请求超时的硬性上限 |
| STDOUT_MAX_BYTES | lib/index.js | 120000 | 每个流的捕获预算 |
| CFG.timeoutMs | lib/client.js | 120000 | 浏览器请求的超时时间 |
| CFG.outputLimit | lib/client.js | 4000 | 卡片中显示的 stdout 字符数 |

安全说明

三道独立的关卡挡在命令前面,三者都必须通过:

1. 部署的浏览器会话。 dsh web 通过 connection 服务签发一个签名的、绑定权限的 cookie,并用它保护页面——但该保护不覆盖插件路由。因此这些路由复用部署自身的判定结果,而不是另造一套方案,并且它们会尝试该服务暴露的两扇门:requestRejection(req)(其自身 API 路由使用的确切策略:受信任主机和* cookie,返回 undefined/401/403)以及更窄的 isAuthenticated(req)(仅 cookie)。只要存在的任一扇门接受请求,请求即通过;两扇门都不存在则拒绝。没有会话的请求会以 401 被拒绝。这一点很重要:在此关卡存在之前,任何本地进程(包括不带 Origin 头的 curl)都可以在这里运行命令,尽管 GUI 本身要求令牌。
2. Origin 围栏(isTrustedRequest):回环 Host 加上同主机 Origin,并拒绝 sec-fetch-site: cross-site——403。这可以阻止其他站点上的页面或 DNS 重绑定名称通过用户的浏览器驱动命令行。它使该功能仅限回环;LAN 部署必须有意放宽该函数。
3. 文件沙箱。 命令通过 ctx.shell 运行,并使用会话解析后的沙箱策略,因此被阻止的写入会以沙箱拒绝的失败形式返回,并附带放宽模式的建议。

connection 是通过从作用域上下文向上攀爬有限数量的父链接来解析的(它不在实时服务目录中,因此不能假定任何单个上下文都会暴露它)。当无法解析它时,路由仍会注册,但会以 401 拒绝每个请求并记录警告——无法验证会话的命令路由绝不能执行,但它必须是可观察的,而不是静默缺席。

永远不要根据 Service Definition 假定方法名。 服务类定义了 isAuthenticated 和 requestRejection,但部署实际发布的对象不一定同时暴露两者:此处一个实时 dsh web 报告

"gateShape": "requestRejection+authorizeIndex"

——isAuthenticated 就是缺席的。一个只调用该方法的关卡
在每个请求上都落到 false,并对浏览器自身的已认证会话返回 401,导致该功能看起来像是认证失败,而实际上是方法名错误。这个教训可以推广:在运行时探测对象的形状(gateShape 字段正是为此而存在),并接受任何存在的、有文档说明的入口,而不是要求某个特定的名称。

GET/POST /cli-mode/probe 故意不进行认证且为只读。它返回 { authenticated, connectionResolved, gateShape, loopback, cookiePresent, host, origin, secFetchSite, headerNames },以便部署能够证明哪个门禁入口被解析、浏览器自身的请求是否携带 cookie,以及 Origin/Sec-Fetch-Site 头是否看起来是同源的。它不执行任何操作,也不泄露任何路径、头值或机密——仅泄露头名称。

在浏览器控制台中,页面自身的 cookie 存在时,这是区分“门禁有问题”和“浏览器没有发送 cookie”的最快方法:

await (await fetch('/cli-mode/probe', { method: 'POST' })).json()

命令路由返回 401,而此处返回 cookiePresent: true 和 authenticated: false,意味着解析到的服务不是部署真正的 connection(检查 gateShape);cookiePresent: false 意味着页面的会话从未建立(重新打开 dsh web 打印的 URL)。

失败是可见的,绝不静默

两条规则塑造了 Host 这一半。两者都来自一次真实的调试会话:插件报告自身已挂载,而所有路由都不存在,唯一的症状是 SPA 回退返回的 405:

1. 注册不得依赖任何可能抛出异常的东西。 路由首先在作用域化的 ctx.inject(['webServer'], …) 内注册;每个注册都是隔离的,因此一个不可用的路由会让其他三个保持挂载,并在日志中点名罪魁祸首(routes live … (failed: /cli-mode/workdir: ))。resolveConnection 和 readJsonBody 分别受到保护:无法遍历的上下文链降级为“无门禁”,格式错误的请求体降级为 400。
2. 无法验证会话的路由会拒绝;它绝不会消失。 在没有可达门禁的情况下,路由仍然注册并返回 401,探测也保持可用。因此,缺失的路由(405/404)只意味着一件事:该行没有挂载。

浏览器这一半遵循同样的原则,来自它自身的一次真实失败:

3. 绝不要假设 bundle 协议不保证的全局变量。 标准客户端 bundle 不是动态插件沙箱:document 不是保证存在的绑定。第一个发布的构建在 apply 开头使用 document.createElement('style'),因此在注册任何内容之前就以 ReferenceError: document is not defined 失败——Host 这一半保持完全健康(探测返回 200,connectionResolved:true,路由强制执行 401),而 composer 从未变红。修复方法是
两部分:防御性地解析文档(裸绑定、window.document、globalThis.window.document),并将样式作为增强——命令行的红色文本、等宽字体和布局是插件自身元素上的内联样式,因此样式表只添加全局覆盖(常驻 composer 的文本颜色)和更美观的表面。verify-client.mjs 特意保留了一个无 document 的用例,正是为了防止这一点发生回归。

4. 链式选择器只接收 owner share——而 composer 链对这个功能来说是错误的工具。 渲染器通过 entry.select(ownerProps) 进行选举,其中 ownerProps 是 { sessionId, session, pendingInteraction };标准的 session props(useInput、inputActions)不在其中,因此读取草稿的选择器必须通过带外方式获取它。更糟的是:composer 附近的两个 dock 槽位(conversation.input.dock 和 conversation.composer.dock)都声明在 composer 栏内部,因此接管 composer 的链式选举会用 display:none 将它们隐藏。结果正是现场报告的情况——“命令模式打开,Enter 不产生结果,Esc 无法退出”——因为结果卡片不可见,而选举所依赖的草稿镜像滞后了一个渲染周期,直接把接管又推了回去。

因此浏览器端不接管 composer。它使用一个已发布的第三方插件(modlens)已经验证过的机制:在捕获阶段监听文档级 keydown,从 composer 自身的 [data-composer-input] 元素读取实时草稿并拦截 Enter/Esc,同时 conversation.input.overlay(一个没有选举的列表槽位,因此它确实能收到标准 props)中的一个空渲染 watcher 翻转 body[data-cli-mode],使常驻 composer 的文本变红。结果卡片位于 conversation.composer.dock,由于 composer 从不被隐藏,它现在始终可见。

通用规则仍然成立:选择器必须对 owner share 保持纯函数,而抛出异常的选择器会静默失败——但对于这个功能,修复方法是完全避开链,而不是给它喂一个草稿镜像。

故障排查

| 症状 | 含义 | 修复 |
| --- | --- | --- |
| POST /cli-mode/exec 返回 405 | 该行未挂载(或编辑后配置文件未重启) | 确认 dsh-cli-mode 在 dsh.profile.bundles 中,然后重启配置文件 |
| 每个路由都返回 401 | session 门控未解析 | 检查 POST /cli-mode/probe → connectionResolved: false;报告插件日志中的警告行 |
| 浏览器返回 403 | 该页面不是此 GUI(外部 Origin/Host) | 使用 dsh web 打印的 URL |
| 对某个路由的 GET 返回 404 | 路由仅支持 POST | 使用带 JSON 正文的 POST |
| 输入框从不变成红色 | 浏览器端没有加载或没有注册 | 打开 DevTools 并检查是否有 cli-mode 错误;典型原因是缺少全局变量(document)在 apply 顶部抛出异常,当前构建对此做了防护 |

为什么路由要借助 ctx.inject

这个 cordis 没有可选注入形式(ctx.get 是唯一的可选读取方式),
而 bundle 层可能在 Web 载体发布 webServer 之前就应用。因此在 apply 时
读取 ctx.get('webServer') 会返回 undefined,路由就会静默地永不注册——
整个功能看起来毫无作用,而插件却报告自己已挂载。路由必须借助作用域化的
ctx.inject(['webServer'], (scope) => …),它在服务出现时运行,
在服务不出现的地方(无头 profile)永不运行,并通过自身的 effect
持有这些注册。

同样的陷阱适用于 bundle 行在 apply 时消费的任何可选服务:当它的缺失必须是
可恢复的时,用 ctx.inject 读取它,并把裸 ctx.get 未命中视为
“在此部署中不可用”——但不要把整个功能链在一个可能永不发布的服务之后,
否则缺失的依赖就会变成一个无法解释的死插件。

开发

三个自包含的验证脚本在纯 Node 上运行(无测试框架,
无构建步骤):

node verify.mjs          # 包级别:导出、路由集、会话门禁和
来源围栏拒绝、逐路由故障隔离、
历史往返、建议指纹、客户端
bundle 协议 + 注册
node verify-profile.mjs  # 针对启动器自身 app-boot 的 profile 接线
辅助函数:bundle 解析、补丁叠加、层栈
node verify-e2e.mjs      # 用打桩的会话门禁和 shell 替身驱动真实路由
处理器,运行实际命令:
成功路径、失败 + 建议、缺失执行器
拒绝、工作目录、来源围栏、历史持久化
node verify-client.mjs   # 加载已发布的 bundle,按渲染器的方式挂载其两个
入口,通过一个小型 fake React 驱动链式 select
和组件,并触发
Enter:断言到达宿主的请求

verify-client.mjs 有意停在请求边界。fake
运行时的 useSyncExternalStore 不是 React 的——通过它断言结果卡片
会把测试框架的产物报告为插件缺陷。卡片、红色
输入框以及接管本身都是浏览器事实;在那里验证它们(在编辑器中
输入 !npm --version,然后检查 Network 面板中是否出现 POST /cli-mode/exec)。

这三个脚本都会按断言打印 PASS/FAIL,并在任何失败时以非零退出。
它们是开发辅助工具,不是已发布运行时的一部分。e2e 测试框架可以
只代替实时进程所提供的内容(ctx.shell);在浏览器中确认 composer 路径本身,其中 /cli-mode/probe 会回答该部署是否曾经解析过会话门控。

由于浏览器那一半是手写的惰性 CJS bundle,而 Host 那一半是一个普通的 ESM 模块,没有任何 @deepseek-ai/ 导入,因此无需构建:编辑 lib/.js,然后重启 profile。

此构建中没有实时挂载

该 profile 声明了 patchReload: live,但随附的 dsh-app-boot 只是导出 watchUserPatches —— 没有任何东西调用它。因此,在运行时写入 cordis.patch.yml 的一行不会被拾取(已验证:写入后路由仍为 404),编辑插件包也有同样的要求。每次更改都需要重启 profile。 不要通过同时将该行插入 cordis.patch.yml 来“修复”此问题:bundle 已经插入了它,第二次插入会将同一行组合两次。

验证部署

重启后,无需浏览器即可观察这些路由。下面的 3080 是 dsh web 默认随附的端口 —— 请替换为它实际打印的端口:

BASE=http://127.0.0.1:3080

curl -s -X POST "$BASE/cli-mode/probe"
-> {"authenticated":false,"connectionResolved":true,...}
connectionResolved:false 表示会话门控未解析(路由 401)
此处 405/404 表示该行未挂载

curl -s -o /dev/null -w '%{http_code}\n' -X POST "$BASE/cli-mode/exec" \
-H 'content-type: application/json' -d '{"command":"!npm --version","sessionId":""}'
-> 401    会话门控被强制执行(从裸 shell 发出时的预期结果)

curl -s -o /dev/null -w '%{http_code}\n' -X POST "$BASE/cli-mode/exec" \
-H 'host: evil.example.com' -H 'content-type: application/json' -d '{"command":"!echo hi"}'
-> 401 或 403   绝不会是 200;门控保持有效

curl -s -X POST "$BASE/cli-mode/exec" \
-H 'content-type: application/json' -H "cookie: " \
-d '{"command":"!npm --version","sessionId":""}'
-> {"ok":true,...,"message":"npm --version 命令已执行","stdout":"",...}

要从 shell 中走完整条路径,请复制 dsh web 打印的已认证 URL(它携带启动令牌),让它设置 cookie,然后用 curl -b 复用该 cookie。在 GUI 中,浏览器会自动发送它。

在 exec 路由上,带有有效请求体的 405 就是上文所述 ctx.get('webServer') 未命中的特征 —— 请检查路由注册是否通过 ctx.inject 进行。

已知限制

- 每次提交只能有一条命令行;调用之间没有 shell 状态(每次运行都是全新的 shell,与 bash 工具完全一样)。
- 无法驱动交互式命令:stdin 未连接,因此提示会一直等待,直到超时终止运行。
- 输出是被捕获的,而不是流式传输的;命令结束后卡片才会出现(运行期间会显示“命令执行中”状态)。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入社群

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

DPharness QQ 群二维码,QQ 扫码进群
QQ 扫码进群
DPharness 飞书群二维码,飞书扫码进群
飞书扫码进群