用键盘鼠标在终端里管理会话、文件与任务
36.1/100
综合分
3
GitHub 星标
42
npm 周下载
—/5
用户评分
数据截至 2026/9/16 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-neotuinpm 源 · dsh-neotui 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · ui
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 50 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-neotui @ 0.4.4
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/31 10:23:07
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 62 处,展示前 54 条)
- 写入本地文件src/index.js:42
mkdirSync(root, { recursive: true }); - 写入本地文件src/index.js:43
appendFileSync(`${root}/tui-error.log`, `${new Date().toISOString()} ${kind}\n${value?.stack ?? value}\n`); - 读取本地文件bin/dsh-tui.js:70
const events = readFileSync(scriptFile, "utf8") - 发起网络请求src/api.js:42
res = await fetch(`${this.base}/api/${method}`, { - 发起网络请求src/api.js:73
res = await fetch(`${this.base}/api/respond`, { - 发起网络请求src/api.js:157
res = await fetch(`${this.base}/api/${method}`, { - 读取本地文件src/views.js:142
try { return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version; } - 读取本地文件src/views.js:151
const version = JSON.parse(readFileSync(file, "utf8")).version; - 请求外部主机registry.npmjs.orgsrc/views.js:166
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, { - 发起网络请求src/views.js:166
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, { - 读取本地文件src/views.js:1835
try { return readFileSync(p, "base64"); } catch { return null; } - 读取本地文件src/views.js:1846
if (!existsSync(full)) continue; - 读取本地文件src/views.js:1849
if (statSync(full).isDirectory()) { - 读取本地文件src/views.js:1853
const buf = readFileSync(full, "utf8"); - 写入本地文件src/views.js:1879
mkdirSync(dir, { recursive: true }); - 写入本地文件src/views.js:1880
appendFileSync(join(dir, "tui-click-debug.log"), `${new Date().toISOString()} ${msg}\n`); - 执行外部命令来自配置/变量src/views.js:2773
const run = spawnSync(cmd[0], cmd[1], { detached: true, stdio: "ignore" }); - 执行外部命令来自配置/变量src/views.js:3751
const res = spawnSync(process.execPath, [helperPath, "--question", q, "--context", ctx], { timeout: 60000, encoding: "utf8", maxBuffer: 8 * 1024 * 102 - 写入本地文件src/views.js:4799
try { writeFileSync(tmp, input.value, "utf8"); } catch (e) { this.toast(`草稿写入失败: ${e.message}`); return; } - 执行外部命令来自配置/变量src/views.js:4801
const res = spawnSync(editor, [file], { stdio: "inherit" }); - 删除本地文件src/views.js:4808
try { unlinkSync(tmp); } catch {} - 发起网络请求src/views.js:4989
const res = await fetch(`${this.api.base}/api/session.export?sessionId=${encodeURIComponent(s.sessionId)}`); - 写入本地文件src/views.js:4995
writeFileSync(file, buf); - 执行外部命令来自配置/变量src/views.js:5605
spawnSync(cmd, [...args, file], { stdio: "inherit" }); - 写入本地文件src/views.js:6871
mkdirSync(dir, { recursive: true }); - 写入本地文件src/views.js:6872
appendFileSync(join(dir, "tui-error.log"), `${new Date().toISOString()} ${e?.stack ?? e}\n`); - 读取本地文件src/widgets.js:396
names = readdirSync(dirResolved, { withFileTypes: true }) - 读取本地文件src/widgets.js:448
names = readdirSync(dirPart, { withFileTypes: true }) - 执行外部命令固定命令src/file-picker.js:24
const mime = execFileSync('file', ['-Lb', '--mime-type', path], { encoding: 'utf8', timeout: 500 }).trim(); - 读取本地文件src/file-picker.js:38
return readdirSync(path, { withFileTypes: true }).filter((e) => hidden || !e.name.startsWith('.')).map((e) => { - 读取本地文件src/file-picker.js:118
if (it.kind === 'text') return readFileSync(it.path, 'utf8').split('\n').slice(0, height).map((x) => truncate(x, width)); - 执行外部命令固定命令src/file-picker.js:119
if (it.kind === 'pdf') { const text = execFileSync('pdftotext', ['-f', '1', '-l', '2', it.path, '-'], { encoding: 'utf8', timeout: 3000 }); return tex - 读取本地文件src/file-picker.js:120
const st = statSync(it.path); - 执行外部命令固定命令src/file-picker.js:123
try { info = execFileSync('magick', ['identify', '-format', '%m · %wx%h', it.path], { encoding: 'utf8', timeout: 2000 }); } catch {} - 执行外部命令固定命令src/file-picker.js:140
let data;try{data=readFileSync(p.path);if(!/ png$/i.test(p.path)){const r=spawnSync('magick',['-','png:-'],{input:data,maxBuffer:32*1024*1024});if(r. - 读取本地文件src/file-picker.js:140
let data;try{data=readFileSync(p.path);if(!/ png$/i.test(p.path)){const r=spawnSync('magick',['-','png:-'],{input:data,maxBuffer:32*1024*1024});if(r. - 读取本地文件src/config.js:24
data = JSON.parse(readFileSync(file, "utf8")); - 写入本地文件src/config.js:37
mkdirSync(dirname(file), { recursive: true }); - 写入本地文件src/config.js:38
writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n"); - 读取本地文件src/panels.js:622
const entries = readdirSync(node.path, { withFileTypes: true }) - 读取本地文件src/panels.js:690
const st = statSync(path); - 读取本地文件src/panels.js:695
const text = readFileSync(path, "utf8"); - 执行外部命令来自配置/变量src/panels.js:872
openItem(external=false){const a=this.items()[this.sel];if(!a)return;if(external){if(!a.path){this.app.toast("这不是本地文件,无法用默认程序定位");return;}try{const cm - 读取本地文件src/panels.js:884
load() { try { this.entries = readdirSync(this.path, { withFileTypes: true }).filter((e) => !e.name.startsWith(".")).sort((a,b) => Number(b.isDirector - 执行外部命令固定命令src/panels.js:1588
try { const child = spawn("wl-copy", ["--type", this.ref?.mediaType ?? "image/png"], { stdio: ["pipe", "ignore", "ignore"] }); child.stdin.end(this.da - 写入本地文件src/panels.js:1639
writeFileSync(tmp, this.data); - 写入本地文件src/panels.js:1653
writeFileSync(tmp, this.data ?? Buffer.alloc(0)); - 执行外部命令来自配置/变量src/panels.js:1656
spawn(cmd, args, { detached: true, stdio: "ignore" }).unref(); - 执行外部命令来自配置/变量src/panels.js:1698
try { const r = spawnSync(cmd, args, { input, timeout: timeoutMs, stdio: ["pipe", "pipe", "ignore"], maxBuffer: 32 * 1024 * 1024 }); return r.status = - 执行外部命令来自配置/变量src/panels.js:1703
return execFileSync(cmd, args, { input, timeout: timeoutMs, encoding: "utf8", stdio: [input ? "pipe" : "ignore", "pipe", "ignore"] }); - 读取本地文件src/theme.js:187
const saved = readFileSync(themeFile(), "utf8").trim(); - 写入本地文件src/theme.js:198
mkdirSync(dirname(themeFile()), { recursive: true }); - 写入本地文件src/theme.js:199
writeFileSync(themeFile(), current + "\n"); - 读取本地文件src/side-question.mjs:65
const creds = parseCredentials(readFileSync(credPath, "utf8"));
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
涉及外部主机:registry.npmjs.org
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 终端内管理会话
- 终端内管理文件
- 终端内管理任务
Neo-TUI:面向 DeepSeek Harness 的鼠标驱动终端 UI 客户端
最近更新
在 GitHub 查看全部 →v0.5.02026-09-14内容较长已截断
· Added
· dsh 0.1.5 协议适配(双协议):src/api.js 的 call()/rpcCall() 内新增 wire 级适配层——方法名翻译表(session.history→session/page、agentPreset.*→agentPresets.*、skill.list→skills/list、subagent.*→subagents.*、host.listDirectory→directoryPicker/list、host.describe→session/modelCatalog、goal.*→goals/* 等)、args 包裹(payload:{args:{...}},含 session/list 的 _request、其余 request 包裹、subagent.prompt 补 requestId/delivery、session.prompt 补 requestId)与返回值还原(records→events、{providers}、{models}、{ref} 等)。连接后自动探测协议(先试 0.1.5 形式,404/未声明端点回退 legacy),protocol 缓存;legacy 路径保持 0.1.2 及更早的旧行为不变(点号方法名 + 平铺 payload)。
· workspace.list 替代:0.1.5 移除了该端点,改由 /api/remote.mux 的 workspace/follow 流首帧 baseline({items, archivedSessionIds})还原;session.history(含 subagent.history)改由 session/follow 首帧快照 + session/page 游标分页实现(游标取自 session/list 的 projections.asOfSeq 或快照 cursor)。
· Host 访问令牌支持:0.1.5 的 /api 需要浏览器会话 cookie,--token / DSH_TUI_TOKEN / base URL 的 ?token= 会先做根路径令牌交换再携带 cookie(HTTP 与 WebSocket 均携带);自托管模式下由 connection 行的 authenticatedUrl 自动注入。tui profile 新增 --token 选项。
· 实时推送通道迁移到 0.1.5 Remote mux:0.1.5 移除 /api/events.mux / /api/events.host 后,审批、提问、会话状态等实时帧改由 WS /api/remote.mux 的逻辑流承载——
- $events 流(首帧 {type:"ready", clientId, host})转发 @deepseek-ai/dsh-api-remotes 白名单:approval/request、user-questions/request(阻塞式 waterfall)与 api-session/added|removed|status|error|activity、commands/change 等 emit 广播;
- 帧按 TUI 既有词汇还原,src/views.js 无需改动:waterfall→approval/requested/question/requested,emit→host/session-added|removed|status、host/agent-error,Host cancel 帧→approval/resolved/question/resolved;
- 回答走一元端点 POST /api/$events/result({clientId, eventId, outcome}):审批结果为 {kind:"result", value:"allowed-once"|"rejected"},提问为 {kind:"result", value:{answers}},取消提问为 {kind:"rejected", error:{name:"UserQuestionError", code:"ASK_CANCELLED"}};未适配的阻塞事件与退出时的挂起项一律回 {kind:"next"},避免 Host 回合被永久挂住;
- 同一 socket 另开 session/control 流,把 baseline/jobs/queue/projection 还原为 session/jobs、session/queue、session/projection(title 额外补 session/title),取代连接期快照轮询;
- 重连退避 500ms→15s
v0.4.42026-09-07内容较长已截断
· Fixed
· 兼容 dsh 0.1.2-rc.1:dsh-base 收编 storage 行与 session-projection-cache 后,组合树同 id 条目按新加载器规则去重(app 补丁仅保留裸 id 覆盖行做 attach 隔离);dsh-host-apiproxy 包停更,其 /api 网关职责并入 connection 行的 node 半端(旧包在新版下启动即崩);补挂 message-feedback 行。
· Added
· 统一前缀体系:Ctrl+Space 面板前缀页与前缀引擎共用同一张表(which-key 模式)——面板内按键与 NORMAL 前缀同义,p 在面板内链入窗格子表(权限迁至 a);Ctrl+H/Ctrl+L 切换主窗口标签页(上一/下一,Shift+Tab 保留;Ctrl+H 原技能键释放);前缀触发字符可在 tui-config.json prefixKeys 段重定义(Ctrl+K 打开配置)。
· 窗口/标签页操作模型重构:会话列表/窗口模式——Ctrl+←→ 或 Tab 在「会话列表 ↔ 主窗口」间切换,Shift+Tab 在主窗口内循环标签页(对话/轨迹/子代理/后台任务,顶部标签条);INSERT 内所有结构键保持编辑语义不冲突;侧栏预览卡片触发键 p(仅列表窗口作用域生效)。
· nvim 式逐层前缀键:NORMAL 下按 p 进入待定层(状态栏提示 + Esc/2s/错误键取消),
- p r/l/u/n:把当前窗口向 右/左/上/下 分屏(新窗口自动成为空白会话窗口,窗口=会话容器;(p c) 关窗后会话保留在侧栏,关掉全部主窗口自动生成空白会话保底);
- p c:关闭当前窗口(焦点自动落到相邻窗口,布局合并恢复);
- 硬护栏:最小 40 列×12 行、窗口 ≤8、嵌套深度 ≤4,超限拒绝并提示。
· Ctrl+W 回收为工作区选择小 buffer:为当前会话选择/移动工作区(workspace.insertSessionBefore),附带 重命名/新建 工作区管理行;旧的大面板仍可从命令面板进入。
· 分屏分隔线鼠标拖动调节比例(0.2–0.8 夹紧,实时重排)。
· 状态栏会话标题前实时显示窗口号前缀(W1 · · …,多窗导航一目了然)。
· 子代理窗格续接输入:i/Tab 聚焦、Enter 发送 subagent.prompt(mode:"continuable")(Esc 回列表;i 仅在子代理窗格局部生效)。
· @ 文件提及:输入中 @src/… Tab 补全(目录内扫描、可循环),发送时非图片文件/目录自动附加为有界文本块(图片走原图片通道,不存在的 @ 静默忽略)。
· 终端 cell 几何探测(CSI 14t/16t):启动探测像素/字符尺寸,Kitty 预览按真实 cell 比例(非假设 2:1)计算,无响应回退。
· 既有未发布功能一并进入本 pre:Tab 路径补全、/rewind 回退分支、Ctrl+Space 前缀面板(s/m/d/p/h/r/c/?/G/e)、键位释放(Ctrl+F///M/D/A/J/N/T/F8)、输入历史持久化+搜索、/btw 侧问(一次性密钥子进程,密钥不进 TUI)、来源徽标(●提问/◇注入/◆目标轮)、侧栏会话预览卡片、场景化帮助(Shift+/?)、外部编辑器草稿(前缀 G)。
· Added
· 欢迎页改版:吉祥物 logo 默认关闭(此前的 40×19 象限块/58×27 半块等实现保留,Ctrl+R 面板可随时启用预设/自定义/关闭;视觉迭代未能达成理想效果,先回退到仅标题)(真实色 255 色调色板,数据内嵌 src/logo.js,去背景、无残块、裁剪掉原图底部黑条);自定义 logo JSON 支持象限格式([mask,fg,bg])与旧半块格式([top,bottom]);品牌行用 █▀▄ 半块像素字 绘制(DEEPSEEK / DSH NEOTUI 两行大字标,5×7 像素字模),每约 3.5 秒一道斜向高光扫过字标(0.9s 扫程,纯 TUI 渲染并随主题语义色,扫过字标全宽含末尾 I;DSH NEOTUI 用深灰底保证白色高光可见);logo 三态 Ctrl+R buffer:预设(内置)/ 自定义(复用 Ctrl+O 与新建工作区的 UploadPicker 单选 {palette, grid} 40×19 JSON)/ 关闭(仅标题),持久化到配置文件;版本更新提示收敛到底部(仅「可更新/失败/检查中」时显示,可点击检查,最新则不提示);模式选择从内联 4 列表收敛为底部提示「模式: · F9 打开模式选择(支持自定义)」——与 WebUI 鼓励自
v0.4.4-pre.02026-08-26预发布内容较长已截断
PRE-RELEASE:npm install dsh-neotui@pre;latest 仍为 0.4.3,不会自动升级。
· Added
· 窗口/标签页操作模型重构:会话列表/窗口模式——Ctrl+←→ 或 Tab 在「会话列表 ↔ 主窗口」间切换,Shift+Tab 在主窗口内循环标签页(对话/轨迹/子代理/后台任务,顶部标签条);INSERT 内所有结构键保持编辑语义不冲突;侧栏预览卡片触发键 p(仅列表窗口作用域生效)。
· nvim 式逐层前缀键:NORMAL 下按 p 进入待定层(状态栏提示 + Esc/2s/错误键取消),
- p r/l/u/n:把当前窗口向 右/左/上/下 分屏(新窗口自动成为空白会话窗口,窗口=会话容器;(p c) 关窗后会话保留在侧栏,关掉全部主窗口自动生成空白会话保底);
- p c:关闭当前窗口(焦点自动落到相邻窗口,布局合并恢复);
- 硬护栏:最小 40 列×12 行、窗口 ≤8、嵌套深度 ≤4,超限拒绝并提示。
· Ctrl+W 回收为工作区选择小 buffer:为当前会话选择/移动工作区(workspace.insertSessionBefore),附带 重命名/新建 工作区管理行;旧的大面板仍可从命令面板进入。
· 分屏分隔线鼠标拖动调节比例(0.2–0.8 夹紧,实时重排)。
· 状态栏会话标题前实时显示窗口号前缀(W1 · · …,多窗导航一目了然)。
· 子代理窗格续接输入:i/Tab 聚焦、Enter 发送 subagent.prompt(mode:"continuable")(Esc 回列表;i 仅在子代理窗格局部生效)。
· @ 文件提及:输入中 @src/… Tab 补全(目录内扫描、可循环),发送时非图片文件/目录自动附加为有界文本块(图片走原图片通道,不存在的 @ 静默忽略)。
· 终端 cell 几何探测(CSI 14t/16t):启动探测像素/字符尺寸,Kitty 预览按真实 cell 比例(非假设 2:1)计算,无响应回退。
· 既有未发布功能一并进入本 pre:Tab 路径补全、/rewind 回退分支、Ctrl+Space 前缀面板(s/m/d/p/h/r/c/?/G/e)、键位释放(Ctrl+F///M/D/A/J/N/T/F8)、输入历史持久化+搜索、/btw 侧问(一次性密钥子进程,密钥不进 TUI)、来源徽标(●提问/◇注入/◆目标轮)、侧栏会话预览卡片、场景化帮助(Shift+/?)、外部编辑器草稿(前缀 G)。
· Added
· 欢迎页改版:吉祥物 logo 默认关闭(此前的 40×19 象限块/58×27 半块等实现保留,Ctrl+R 面板可随时启用预设/自定义/关闭;视觉迭代未能达成理想效果,先回退到仅标题)(真实色 255 色调色板,数据内嵌 src/logo.js,去背景、无残块、裁剪掉原图底部黑条);自定义 logo JSON 支持象限格式([mask,fg,bg])与旧半块格式([top,bottom]);品牌行用 █▀▄ 半块像素字 绘制(DEEPSEEK / DSH NEOTUI 两行大字标,5×7 像素字模),每约 3.5 秒一道斜向高光扫过字标(0.9s 扫程,纯 TUI 渲染并随主题语义色,扫过字标全宽含末尾 I;DSH NEOTUI 用深灰底保证白色高光可见);logo 三态 Ctrl+R buffer:预设(内置)/ 自定义(复用 Ctrl+O 与新建工作区的 UploadPicker 单选 {palette, grid} 40×19 JSON)/ 关闭(仅标题),持久化到配置文件;版本更新提示收敛到底部(仅「可更新/失败/检查中」时显示,可点击检查,最新则不提示);模式选择从内联 4 列表收敛为底部提示「模式: · F9 打开模式选择(支持自定义)」——与 WebUI 鼓励自定义预设的方向一致;矮视口( · F9 打开模式选择(支持自定义)」——与 WebUI 鼓励自定义预设的方向一致;矮视口(」),Enter 或双击应用后停留继续调整(控制类 buffer 不再因选择而退出),Esc 恢复已提交主题并关闭;快捷键可重映射(themePicker)。
· pane 焦点切换拆分为两个独立命令:panePrev(Ctrl+←)与 paneNext(Ctrl+→)——与 [ / ] 一样各自可重映射,不再共用一个主/备槽位绑定;旧配置里的 homeSwitch(如 "Ctrl+Left/Right" 或自定义方向映射)自动迁移为两个方向。
· 0.4.0 — 2026-08-21
· Changed
· 搜索 buffer 优化:顶栏精简为 Shift+/ 帮助,完整快捷键与索引/降级边
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-neotui
dsh-neotui 是 DeepSeek Harness 的 keyboard-first 终端客户端:以 Vim/Nvim 风格的 NORMAL / INSERT / VISUAL 只读导航、tmux 风格 pane 焦点和 Yazi 风格文件选择器为主,鼠标作为辅助,访问与 WebUI 相同的 Host 会话、工具、审批、任务和设置能力。
本仓库发布两个 npm 包:
| 包 | 用途 |
| dsh-neotui | TUI 客户端、核心界面和 dsh-neotui 命令 |
| dsh-neotui-app | 将 TUI、API gateway 和 Host 服务装配成 DSH profile 的 bundle |
0.2.0 亮点
- 三栏 Yazi 风格文件与工作区选择器:路径编辑、模糊筛选、隐藏项、Nerd Font 图标和 Kitty 图片预览;列表、预览与 @ 补全全部通过 workspaceFiles/ / fileReferences/list 走 Host,远端 attach 时看到的就是 Host 的文件系统;
- 「文件/改动」主窗口标签:订阅 Host workspaceFiles/changes,按路径列出 M/A/D,Enter 读取内容(长文件分页、带 patch 时按 diff 配色),r 重新订阅;
展开完整文档(README 剩余 1 节)
dsh-neotui
dsh-neotui 是 DeepSeek Harness 的 keyboard-first 终端客户端:以 Vim/Nvim 风格的 NORMAL / INSERT / VISUAL 只读导航、tmux 风格 pane 焦点和 Yazi 风格文件选择器为主,鼠标作为辅助,访问与 WebUI 相同的 Host 会话、工具、审批、任务和设置能力。
本仓库发布两个 npm 包:
| 包 | 用途 |
| dsh-neotui | TUI 客户端、核心界面和 dsh-neotui 命令 |
| dsh-neotui-app | 将 TUI、API gateway 和 Host 服务装配成 DSH profile 的 bundle |
0.2.0 亮点
- 三栏 Yazi 风格文件与工作区选择器:路径编辑、模糊筛选、隐藏项、Nerd Font 图标和 Kitty 图片预览;列表、预览与 @ 补全全部通过 workspaceFiles/ / fileReferences/list 走 Host,远端 attach 时看到的就是 Host 的文件系统;
- 「文件/改动」主窗口标签:订阅 Host workspaceFiles/changes,按路径列出 M/A/D,Enter 读取内容(长文件分页、带 patch 时按 diff 配色),r 重新订阅;
- 图片附件栏、附件管理器、dd 删除和等比例 Kitty 预览;非图片文件经 fileUploads/upload 成为真正的 Host 附件(以 receiptId 随消息发送);
- 全屏跨会话全文搜索:工作区→会话→匹配块树、附近内容预览和精确跳转;
- NORMAL / INSERT / VISUAL 只读模式、正文块选择与可编辑的快捷键目录;
- 独立的命令、设置和插件目录,插件支持即时筛选;
- Queue / Steering、Goal、TODO、Plan Review、后台任务和 Subagent 状态;
- grapheme-aware framebuffer、CJK/组合字符、Kitty keyboard、SGR mouse、OSC 8/52。
完整变化见 CHANGELOG.md。
安装与启动
需要 Node.js 22 或更高版本(客户端使用 Node 内置 fetch、WebSocket、crypto.randomUUID 和 Intl.Segmenter)。
独立 DSH profile
dsh plugin --profile dsh-neotui add dsh-neotui-app
dsh --profile dsh-neotui
常用参数:
dsh --profile dsh-neotui --session
dsh --profile dsh-neotui --cwd ~/work
dsh --profile dsh-neotui --host 127.0.0.1 --port 3981
出于安全原因,--host 0.0.0.0 会被拒绝。能够执行工具的 Host 不应直接暴露到不受信任的网络。
连接已有 Web Host
dsh --profile dsh-neotui --attach 3080
dsh 0.1.5 起 Host 的 /api 需要访问令牌(令牌随 dsh web 启动时打印的 URL 一起给出)。attach 到这类 Host 时补上 --token(或设 DSH_TUI_TOKEN,或把令牌写进 --base 的 ?token=):
dsh --profile dsh-neotui --attach 3080 --token
自托管模式(不带 --attach)由进程内的 connection 行自动注入令牌,无需手动指定。客户端在连接后会探测 Host 版本:旧版(≤0.1.2)走点号方法名 + 平铺 payload,0.1.5 走 namespace/method + args。
也可以直接运行客户端:
node bin/dsh-tui.js
node bin/dsh-tui.js --base http://127.0.0.1:3080
node bin/dsh-tui.js --base http://127.0.0.1:3080 --token
默认连接 http://127.0.0.1:3080;可通过 --base、DSH_URL 或 DSH_WEB_URL 覆盖。--attach 不会启动或替换 WebUI。
从源码运行
.
├── app/ dsh-neotui-app bundle
├── bin/dsh-tui.js 客户端入口 ├── src/ TUI 核心 └── test/ 单元、终端协议与 PTY 测试
使用本地 profile:
mkdir -p ~/.dsh/profiles/node_modules
ln -sfn "$(pwd)" ~/.dsh/profiles/node_modules/dsh-neotui
ln -sfn "$(pwd)/app" ~/.dsh/profiles/node_modules/dsh-neotui-app
dsh --profile dsh-neotui
只调试客户端时,保证目标 Host 已运行即可:
node bin/dsh-tui.js --base http://127.0.0.1:3080
交互模型
NORMAL / INSERT
- NORMAL:单字符用于导航和操作;
- INSERT:键盘输入交给消息编辑器;
- i 或点击输入框进入 INSERT;
- Esc 离开 INSERT;
- INSERT 中 Esc 不会中断当前回合;
- NORMAL 中 Esc 会中断正在运行的回合,否则返回上一级;
- NORMAL 中连续两次 Ctrl+C 退出,INSERT 中 Ctrl+C 清空输入。
底栏始终显示当前模式。按 Ctrl+Space 打开快捷键、命令、设置和插件目录。
输入与附件
| 模式 | 按键 | 功能 |
| INSERT | Enter | 发送 |
| INSERT | Shift+Enter / Ctrl+J | 换行 |
| INSERT | Ctrl+L | 展开/折叠输入栏 |
| INSERT | ↑ / ↓ | 在首尾行浏览输入历史 |
| INSERT | Ctrl+Shift+C | 复制输入框选区 |
| INSERT | Ctrl+Z / Ctrl+Y | 撤销 / 重做(发送后清空历史) |
| INSERT | Ctrl+O | 打开文件选择器 |
| INSERT | Ctrl+Shift+V | 粘贴剪贴板图片 |
| INSERT | Esc | 退出输入(可重映射;Esc 始终保留兜底) |
| NORMAL | Ctrl+O | 打开附件管理器 |
输入框草稿按会话镜像:切换到另一会话时保存当前未发送文本,切回时原样恢复(发送后清空)。
文件选择器支持:
| 按键 | 功能 |
| ↑ / ↓ | 移动光标 |
| ← / → | 返回上级 / 进入目录 |
| Space | 选择或取消文件 |
| Enter | 确认上传 |
| / | 筛选当前目录 |
| Ctrl+/ | 清除筛选并退出筛选模式 |
| Ctrl+F | 编辑路径,支持 ~、$HOME 和环境变量 |
| Ctrl+. | 显示/隐藏隐藏项 |
| Esc | 关闭 |
附件管理器支持 Enter 预览、Shift+Enter 或双击用默认程序打开、dd 删除。图片以 {type:"image"} 内联内容块随消息发送;非图片文件在 dsh 0.1.5 Host 上会成为真正的 Host 附件:TUI 通过 fileUploads/upload({agentId, request:{data, name}})把文件字节上传到 Host,拿到 receiptId 后以 {type:"file", receiptId} 随 session/prompt 发送,Host 在收件时校验该回执。只有 Host 缺少该接口(0.1.5 以前的旧 Host)时,条目才退化为「仅元数据」(名称 / 大小 / 类型),绝不伪装成可发送附件。
Kitty graphics 可用时,图片在文件选择器和附件预览中等比例显示;否则回退到 MIME、尺寸和文件大小信息。
Queue / Steering
模型运行时,Enter 的策略由 busyEnter 决定:
- queue:加入下一回合队列;
- steer:追加到当前回合。
Ctrl+Y 切换策略,Ctrl+N 打开排队命令详情。队列面板每条命令一行,使用 ↑/↓ 循环选择、Enter 展开详情、Ctrl+↑/↓ 独立滚动详情、dd 删除、Esc 关闭。
快捷键
以下是默认绑定。控制面板中的快捷键页以 MODE / KEY1 / KEY2 / FUNCTION 四列显示,每个功能拥有主/备两个槽位,并允许编辑用户覆盖;配置写入 $DSH_HOME/tui-config.json。这些绑定是运行时的唯一来源:修改后立即生效(无需重启),全部定义集中在 src/keybindings.js。
| 模式 | 按键 | 功能 |
| ALL | Ctrl+Space / F7 | 控制面板 |
| NORMAL | Ctrl+F / / | 打开全屏跨会话全文搜索;Enter 执行,/ 重新编辑 |
| NORMAL | Ctrl+B | 显示/隐藏侧栏 |
| NORMAL | Ctrl+M | 模型与思考强度 |
| NORMAL | F8 | 权限模式轮换 |
| NORMAL | F9 | Agent 模式 |
| NORMAL | Ctrl+W | 工作区 |
| NORMAL | Ctrl+Shift+W | 添加工作区 |
| NORMAL | Ctrl+T | 轨迹视图 |
| NORMAL | Ctrl+← | pane 焦点移到上一窗格(工作区栏 / 对话 / 轨迹循环) |
| NORMAL | Ctrl+→ | pane 焦点移到下一窗格(两个方向独立可重映射) |
| NORMAL | Ctrl+E | 按 step 快速跳转 |
| NORMAL | Ctrl+J | 后台任务与 Subagent |
| NORMAL | Ctrl+N | 排队命令详情 |
| NORMAL | Ctrl+Y | 运行中 Enter 策略(追加 / 排队) |
| NORMAL | Ctrl+O | 附件管理 |
| NORMAL | Ctrl+G | Goal / TODO |
| NORMAL | Ctrl+S | Settings |
| NORMAL | Ctrl+A | Subagent |
| NORMAL | Ctrl+H | Skills |
| NORMAL | Ctrl+K | 用默认编辑器($EDITOR / $VISUAL / vi)打开 tui-config.json;编辑完成后快捷键立即重载 |
| NORMAL | Ctrl+Shift+C | 复制正文选区/当前块(与 y 等价) |
| NORMAL | Ctrl+D | 配色主题选择器(/theme 同义) |
| NORMAL | Ctrl+C | 双击退出(第一次按仅提示) |
| ALL | Ctrl+Q | 退出 |
| 正文块 | ↑ / ↓、j / k | 上下选择正文块,两端停留不环绕;Ctrl+↑/↓ 只滚动视口 |
| 正文块 | Space / Enter / Ctrl+R | 折叠块 / 进入只读光标 / 打开上下文菜单(消息级菜单含「从此消息之后分叉」) |
| NORMAL 光标 | h l w b e 0 $、v / V | Vim 式只读移动与字符/整行 VISUAL(无 x / d) |
| NORMAL / VISUAL | y / Ctrl+Shift+C | 复制当前代码块/正文块或选区 |
| 正文块 | t / b | 全局展开/折叠思考块 / 工具块 |
| 正文块 | g g / G | 首个 / 最新正文块;G 选中最新块并让块头落在视口底部 |
| NORMAL | [ / ] | 上一个 / 下一个提问终点 |
| NORMAL | PgUp / PgDn | 翻页;到顶时加载更早历史 |
When the sidebar is focused, ↑/↓ cycles through workspaces or sessions, Space expands/collapses a workspace, Enter opens a session while keeping sidebar focus, n creates a new session, i enters input, and Ctrl+R opens the current item menu. When the trace is focused, ↑/↓ cycles through steps, Space expands the full event, Enter jumps back to the conversation, and Ctrl+R opens the step menu.
The search buffer top bar keeps only Shift+/ Help; pressing Shift+/ (terminals usually send ?) opens the full shortcut and search scope description, and pressing it again or pressing Esc returns. During the input stage, ↑/↓ recalls the most recent 20 persisted queries; Enter executes the query or jumps to a result, / returns to query editing, s cycles between relevance / most recently updated / hit count sorting, Space collapses workspaces/sessions, t / b collapses thought/tool matches, PgUp / PgDn / Home / End page through the result list, and Ctrl+↑/↓ and Ctrl+PgUp/PgDn scroll the right-side preview. Mouse: clicking a result row selects it, double-clicking jumps (double-clicking a workspace/session toggles collapse), the scroll wheel scrolls the list/preview/help page by position, and clicking the query row returns to the input stage; after a terminal resize, the preview re-anchors to the hit block. During parsing, per-session progress is displayed (Host candidates show the parsed count, local scans show the scanned and hit counts; Esc can cancel at any time), result rows and the preview both scroll with the selected item and highlight query terms, hit-term highlighting is computed once and colored across line-wrap boundaries (huge tool results will not slow down rendering); session rows show the hit count, with the latest match within a session first; for sessions that have Host hits but are not in the sidebar list, the latest event time from their history tail page is used in the "most recently updated" sort. Switching the sort preserves the currently selected session/match row. After jumping, only query terms within the hit block are highlighted, and Esc clears the highlight. If the Host has not mounted the search index (session.search unavailable), it automatically degrades to a bounded local scan of the recent history of the most recent 20 sessions, and shows a notice at the top of the buffer.
In the shortcut catalog: Enter edits the current JSON config item ({"mode":"normal|insert|all","key":"…","key2":"…"}, key2 may be empty), Shift+Tab cycles between NORMAL / INSERT / ALL, and Alt+Enter restores the default. Before saving, the JSON, mode, and both key slots are validated; error text is preserved so you can continue editing. Two-press key combinations (such as g g) are written separated by a space.
Slash Commands
After entering /, use Tab, ↑, ↓ to complete. The candidate bar first lists the Host command catalog (commands/list, showing the input.hint parameter hint), then appends TUI local commands. TUI local commands include:
| Command | Function |
| /reload | Redraw and reload the interface state |
| /restart | Restart the TUI and restore the session handoff |
| /model | Model selection |
| /theme | Switch theme |
| /permission | Permission selection |
| /goal | Goal panel |
Host-provided commands such as /compact, /export, /feedback, and /plan appear dynamically on the command page; the actual list is based on the current Host's commands/list.
Main Window Tabs and "Files/Changes"
The main window tabs are Conversation | Trace | Subagents | Background Tasks | Files/Changes, cycled with Shift+Tab (structural key), Ctrl+H / Ctrl+L, or by clicking the tab row directly.
"Files/Changes" subscribes to the Host's workspaceFiles/changes stream (after {kind:"ready"}, each observed write produces a frame {kind:"change", change:{absolutePath, version|absent:true}}), accumulating into one row per path: M/A/D status, path relative to the workspace, size (also displayed when the payload carries ±lines or a patch). ↑/↓ selects, Enter uses workspaceFiles/read to read the file content from the Host in pages (PgDn loads the next page; when the payload carries a patch, it is rendered with the existing diff color scheme), h/Backspace returns to the list, r resubscribes and re-stats each row, q/Esc returns to the conversation; if the Host has not observed any changes, an empty state is shown.
All file reads go through the Host: the file picker's directory listing and preview (workspaceFiles/list, workspaceFiles/read, image bytes readAll), @ mentions and path completion (fileReferences/list, Host index), the workspace tree, overflow output previews, and attachment bytes. Therefore, when the TUI is attached to a remote Host, what you see is the Host's filesystem, not a same-named file on the local disk; only local files such as $EDITOR drafts and tui-config.json are still read directly from the local machine. List/preview results have a TTL asynchronous cache and request deduplication, so rendering and keypresses do not re-hit the Host every frame.
Panels and Tool Cards
The TUI supports:
- Workspace and grouped session tree: create, open, rename, move, archive, delete, and export;
- terminal, read, search, web, diff, and generic presentation; minimized JSON (such as workflow results) is automatically laid out with 2-space indentation into a multi-line readable structure (body code blocks and thought content are left untouched);
- Tool approval, AskUser single/multiple choice, Plan Review;
- Goal, TODO, background tasks, and Subagent;
- Standalone plugin list with / filtering;
- Blank session welcome page: the mascot logo is hidden by default (Ctrl+R opens the logo panel: preset (40×19 quadrant blocks 80×38 true color) / custom JSON / off); the brand line is drawn with █▀▄ half-block pixel characters (two large wordmarks, DEEPSEEK / DSH NEOTUI), and a diagonal highlight sweeps across the wordmark about every 3.5 seconds (pure TUI rendering, following the theme's semantic colors); logo has three states (Ctrl+R opens the buffer): preset (built-in) / custom (Yazi-style file picker single-select JSON, format {palette, grid} 40×19) / off (title only), with the choice persisted to the config file; version update notices are collapsed to the bottom (DeepSeek Harness can update v… / dsh-neotui can update v…, clickable to check, not shown when up to date); mode selection is a bottom hint (F9 opens mode selection (custom supported)); in short viewports (」),Enter 或双击应用并停留继续调整,Esc 恢复已提交主题并关闭;选择持久化到 $DSH_HOME/tui-theme.txt。
- 启动动画(经典游戏启动器风格,默认关闭的 meme):纯黑 → 白色从屏幕中心圆扩散到全屏(约 1s)→ 四行健康游戏忠告("抵制不良游戏,拒绝盗版游戏。"等,逐句淡入,约 2s)→ 忠告淡出同时 DEEPSEEK 半块大字交叉淡入(约 2s)→ 底部闪烁「按任意键进入游戏」,按任意键进入。全程纯 ANSI(█▀▄);dsh-tui --launcher-anime(或 DSH_TUI_LAUNCHER_ANIME=1)显式启用,DSH_TUI_NO_SPLASH=1 强制关闭,--script 模式不播放。
工作区(Ctrl+W)、设置(Ctrl+S)、模型供应商(模型选择器里的 ⚙ 管理供应商…)、子代理(Ctrl+A)和技能(Ctrl+H)都是全屏模态 Buffer:打开后覆盖整个界面,Esc 逐级返回并最终关闭。它们不再占用“标签页模式”,因此关闭后 Ctrl+←/→ 的 pane 聚焦立即恢复,两者互不冲突。轨迹仍是 pane 序列的一部分,由 Ctrl+T 或 Ctrl+←/→ 进入。
模型选择器(Ctrl+M / /model)是供应商文件夹 → 具体模型的层级结构:Space 展开/折叠文件夹,↑/↓ 移动,Enter 确认具体模型(带思考强度时再选一档),当前模型以 ● 标记并默认展开、默认选中。筛选与其他 buffer 一致:/ 进入筛选、Ctrl+/ 退出筛选,普通字符在浏览模式下不会误触发筛选。
所有 Buffer 都是模态的:点击外部只会吞掉事件,不会关闭 Buffer。退出必须使用界面明确提示的按键或操作。
鼠标与终端能力
- 点击工作区、会话、标签、工具块和输入框;
- 拖动侧栏分隔线;
- 滚轮浏览对话、列表和弹窗;
- 输入框拖选并通过 OSC 52 复制;
- 右键消息、轨迹 step 和工作区树打开菜单;
- SGR mouse、bracketed paste、Kitty keyboard、OSC 8、OSC 52;
- ANSI truecolor 差量 framebuffer;
- CJK、组合字符和 ZWJ emoji 的 grapheme-aware 渲染。
不同终端、tmux 和 SSH 环境对 Kitty graphics、keyboard、OSC 52 的支持不同。WezTerm 和 Kitty 是图片预览的推荐终端。
实时更新(0.1.5 Host)
dsh 0.1.5 不再转发 session/event,也不落库 assistant/chunk:一次回合里持久化日志只在
模型流结束后写入 assistant/message,因此单靠轮询只能在回合结束后看到整段回答。对话主通道因此改为 Remote 流 session/follow(mode:"stream",request:{address, maxMessages, assistantStream:true}):
- 打开/切换会话时对当前会话开流;切换、恢复、/reload 与退出都会换流/关流;socket 断开按退避重连并在新连接上重开(Host 会重发快照)。
- 首帧 snapshot 给出开场窗口与游标 cursor(开场窗口最后一个已提交 seq),TUI 据此发出 session/subscribed;随后是 cursor+1 起严格递增的 {type:"event", event} 记录。
- assistant-stream 增量帧是 0.1.5 唯一的分词级实时文本来源;中途接入会话时用快照里的累积流回放已生成文本。
- 同一事件可能同时被流和轮询看到:两条路径共用一个 seq 游标(ChatView.acceptRecords),只有严格递增的记录会被应用,重复/乱序一律丢弃。
轮询仍在:session/follow 健康时它退化为约 15 秒一次的安全网(只做对账);流不可用(旧协议 Host、mux 不可达、流出错)时恢复原有节奏,并一次性 toast 提示。
工作区/归档没有推送:Host 不广播工作区事件,侧栏只能定时刷新——会话列表 窗口聚焦时约 2 秒(进入该窗口时立即刷一次),其余情况约 5 秒;本机的重命名/归档/新建等操作仍会立即刷新。
配置
TUI 设置:
$DSH_HOME/tui-config.json
包含显示名、默认折叠状态、运行中 Enter 策略和快捷键覆盖。主题保存在:
$DSH_HOME/tui-theme.txt
DSH_TUI_USER_PREFIX 可覆盖默认用户名。
测试
npm test # 单元与协议测试
npm run test:pty # 真实 PTY 生命周期
npm run test:rc # 完整发布候选验证
PTY 测试会验证 alternate screen、SGR mouse、界面渲染、退出恢复和常见运行时错误;有私有 Host 可用时还会播种会话并断言 session/list/session/page 数据到达界面、审批推送($events waterfall + $events/result)以及实时增量渲染(session/follow:回合仍在流式输出时增量文本已出现在渲染帧里,轮询在该时刻不可能提供这段文本)。需要可用的 DSH Host;Host 不可用时测试会明确输出 SKIP。
脚本化 smoke:
node bin/dsh-tui.js --script test/smoke.script --plain
当前限制
- TUI 与 Host 必须使用兼容的事件、RPC 和内容块契约;
- 工作区/归档没有推送:Host 不广播工作区变更($events 白名单只有 api-session/ 与 commands/change),其它客户端或守护进程改动分组/归档时,TUI 只能靠定时刷新感知(会话列表 聚焦 ~2s、其余 ~5s),本机操作则立即刷新;
- session/follow 的基线回放是进程内状态:回合进行中接入会话时,已生成文本来自 Host 进程内的累积流;若此时 Host 进程重启,该回合剩余文本只能等 assistant/message 落地后才出现(Host 侧限制,TUI 无法补出已丢失的增量);
- 二进制附件依赖 dsh 0.1.5:非图片文件通过 fileUploads/upload + {type:"file", receiptId} 成为真正的 Host 附件;旧 Host 没有该通道时,条目退化为「仅元数据」(名称/大小/类型)且不会发送;工具结果若含原始二进制字节(NUL/控制字符),工具卡同样只显示二进制数据元数据而不渲染字节(Host 侧协议限制,TUI 无法单独扩展);
- 文件改动流只覆盖 Host 插入式写入:workspaceFiles/changes 转发的是 fs/observed(Host 组合文件系统上的写入/编辑工具等)。子进程、shell 命令或用户编辑器直接改文件不会产生任何帧(实测确认),因此「文件/改动」页只显示本会话内被 Host 观测到的改动;
- Kitty 图片效果受终端实现、cell 尺寸和复用器支持影响;
- 超长工具输出由 Host 截断并给出恢复路径:TUI 检测 [output truncated; full output: ] / stored at: 类提示,在工具卡内显示完整输出文件,选中该块后 Ctrl+R 菜单可 host.openPath 打开、复制路径;「TUI 内预览完整输出」通过 workspaceFiles/read 从 Host 读取该文件(上限 500 行),因此远端部署同样可用,只有 Host 上没有该文件时才回退到本机同路径读取;
- 可编辑快捷键覆盖配置已持久化并经过校验;输入/编辑/全局快捷键均已接入动态 dispatch,Esc 退出输入、Ctrl+Q 退出与审批弹窗 y/n 等安全键保持固定语义。
代码结构
src/api.js HTTP RPC、WebSocket 和 respond src/term.js raw mode、鼠标、paste、Kitty keyboard src/screen.js cell framebuffer 与 ANSI diff src/text.js grapheme、显示宽度和截断 src/md.js Markdown、代码块和 OSC 8 src/keybindings.js 可编辑快捷键注册表(每个功能主/备两个槽位) src/widgets.js Input、Popup、ScrollView、Menu、StatusBar src/views.js App、ChatView、会话树和主路由 src/panels.js Workspace、Trajectory、Queue、Jobs、Settings src/file-picker.js 三栏文件/目录选择器与图片预览(列表/预览走 Host) src/host-files.js Host 文件访问(workspaceFiles/、fileReferences/、改动流、附件上传)与异步缓存 app/ DSH bundle 与 Cordis patch
许可证
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
edabchann/dsh-neotui
用键盘鼠标在终端里管理会话、文件与任务
相关插件 · UI / 主题
破甲增强版JeffKeeping/dsh-purge-plus
dsh-purge 的非官方分支fork:适配 DeepSeek Harness 0.2.1-rc.2、破甲内核…
★ 387已汉化DeepSeek 客户端
71.9
无代码业务系统平台nocobase/nocobase
NocoBase 是什么
★ 24,804已汉化DSH
71.8
1Panel 服务器面板1Panel-dev/1Panel
全球 250 万+ 自托管用户共同喜爱。
★ 37,350已汉化DSH
71.5
工作状态行动态ccch1mneyyy/working-activity
作者:chimney@ccch1mneyyy。社区项目,非 DeepSeek 官方项目。
★ 664已汉化DSH
71.2
多模型配置管家farion1231/cc-switch
跨平台桌面一体化助手,支持 Claude Code、Codex、OpenCode、DeepSeek Harness 等工具,唯一官网 ccswitch.io
★ 151,506已汉化DSH
71.1
AI 矢量设计工具ZSeven-W/openpencil
全球首个 AI 原生开源矢量设计工具。
★ 7,027已汉化DSH
71.1
这是你的仓库?认领条目