用 Neovim 当渲染壳 的 DeepSeek Harness dsh TUI:聊天区就是普通 nvim…
数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
dsh plugin --profile web add kovey/dsh-nvim-tui状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
安装兼容性检查需源码安装engines.node 要求 >=23.6,不满足 Node 22.19.0
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/28 23:37:28
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
查看扫描证据(共 135 处,展示前 118 条)
- 写入本地文件lib/kernel/app.js:156
appendFileSync(errorLogPath, `${new Date().toISOString()} ${label}: ${e?.stack ?? String(err)}\n`); - 写入本地文件lib/kernel/app.js:311
appendFileSync(errorLogPath, `${new Date().toISOString()} 进程诊断: ${kind}: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`); - 发起网络请求lib/ext-api/index.js:176
const p = app.slices.runtime.nvim.request(method, args); - 读取本地文件lib/sessions/index.js:128
const head = readFileSync(path).subarray(0, 4); - 写入本地文件lib/sessions/index.js:152
writeFileSync(path + '.tmp', encodeHeaderOnlyLog(headerLine)); - 写入本地文件lib/sessions/index.js:153
renameSync(path + '.tmp', path); - 读取本地文件lib/sessions/index.js:216
return JSON.parse(readFileSync(statePath, 'utf8')); - 写入本地文件lib/sessions/index.js:232
writeFileSync(statePath, JSON.stringify({ ...prev, sessionId: id, cwd, at: Date.now() })); - 写入本地文件lib/sessions/index.js:355
appendFileSync(app.errorLogPath, `${new Date().toISOString()} ${t('恢复会话失败')} ${targetId}: ${message}\n`); - 读取本地文件lib/transcript/index.js:247
const st = await stat(abs); - 读取本地文件lib/transcript/index.js:250
const text = await readFile(abs, 'utf8'); - 读取本地文件lib/kernel/subagent-clean.js:66
const j = JSON.parse(readFileSync(cleanStatePath(), 'utf8')); - 写入本地文件lib/kernel/subagent-clean.js:82
writeFileSync(cleanStatePath(), JSON.stringify({ cleaned })); - 读取本地文件lib/sessions/prefs.js:17
return JSON.parse(readFileSync(statePathOf(), 'utf8')); - 写入本地文件lib/sessions/prefs.js:27
writeFileSync(statePathOf(), JSON.stringify({ - 读取本地文件lib/sessions/services.js:106
if (!statSync(abs).isDirectory()) - 发起网络请求lib/feed/feed.js:419
this.cardNs = await this.nvim.request('nvim_create_namespace', ['dsh_tui_extcards']); - 发起网络请求lib/feed/feed.js:427
await this.nvim.request('nvim_buf_del_extmark', [this.bufId, ns, prev.markId]).catch(() => { }); - 发起网络请求lib/feed/feed.js:431
markId = await this.nvim.request('nvim_buf_set_extmark', [ - 发起网络请求lib/feed/feed.js:459
await this.nvim.request('nvim_buf_del_extmark', [this.bufId, ns, prev.markId]); - 发起网络请求lib/feed/feed.js:1172
this.nvim.request('nvim_win_get_height', [ids.win]), - 发起网络请求lib/feed/feed.js:1173
this.nvim.request('nvim_win_get_width', [ids.win]), - 写入本地文件lib/transcript/commands/export.js:26
writeFileSync(path, text); - 发起网络请求lib/transcript/commands/export.js:30
const lines = await app.slices.runtime.nvim.request('nvim_buf_get_lines', [rec.feed.bufId, 0, -1, false]); - 写入本地文件lib/transcript/commands/export.js:32
writeFileSync(path, `# ${rec.title ?? rec.id}\n\n` + lines.join('\n') + '\n'); - 读取本地文件lib/commands/core.js:288
for (const name of readdirSync(dirPath, { withFileTypes: true })) { - 写入本地文件lib/commands/core.js:549
appendFileSync(app.errorLogPath, `${new Date().toISOString()} 命令 ${name}: ${e?.stack ?? String(err)}\n`); - 读取本地文件lib/commands/commands/doctor.js:17
for (const proj of readdirSync(root)) { - 读取本地文件lib/commands/commands/doctor.js:20
if (!statSync(pd).isDirectory()) - 读取本地文件lib/commands/commands/doctor.js:26
for (const sid of readdirSync(pd)) - 写入本地文件lib/commands/commands/remember.js:16
mkdirSync(dir, { recursive: true }); - 写入本地文件lib/commands/commands/remember.js:17
appendFileSync(join(dir, 'global.md'), `- ${a}\n`); - 读取本地文件lib/commands/commands/memory.js:23
if (!existsSync(file)) { - 删除本地文件lib/commands/commands/memory.js:35
unlinkSync(file); - 读取本地文件lib/commands/commands/memory.js:39
if (!existsSync(dir)) { - 读取本地文件lib/commands/commands/memory.js:43
for (const f of readdirSync(dir, { recursive: true, encoding: 'utf8' }).filter((x) => x.endsWith('.md'))) { - 读取本地文件lib/commands/commands/plugins.js:51
names = readdirSync(profilesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name); - 读取本地文件lib/commands/commands/plugins.js:78
if (!existsSync(patchPath)) { - 读取本地文件lib/commands/commands/plugins.js:84
text = readFileSync(patchPath, 'utf8'); - 写入本地文件lib/commands/commands/plugins.js:121
copyFileSync(patchPath, backup); - 写入本地文件lib/commands/commands/plugins.js:122
writeFileSync(patchPath, edit.text, 'utf8'); - 读取本地文件lib/commands/commands/upload.js:30
if (!existsSync(path)) { - 读取本地文件lib/commands/commands/upload.js:36
size = statSync(path).size; - 读取本地文件lib/commands/commands/upload.js:52
const data = readFileSync(path); - 读取本地文件lib/commands/commands/files.js:12
if (!existsSync(abs)) { - 读取本地文件lib/commands/commands/files.js:18
if (statSync(abs).isFile()) - 读取本地文件lib/commands/commands/preview.js:22
if (!existsSync(abs)) { - 读取本地文件lib/commands/commands/preview.js:28
size = statSync(abs).size; - 读取本地文件lib/commands/commands/preview.js:45
const data = readFileSync(abs); - 读取本地文件lib/commands/commands/deliverables.js:86
if (!existsSync(sel)) { - 读取本地文件lib/commands/commands/deliverables.js:92
size = statSync(sel).size; - 读取本地文件lib/commands/commands/settings.js:120
if (!existsSync(path)) - 写入本地文件lib/commands/commands/settings.js:121
writeFileSync(path, 'refs:\n', { mode: 0o600 }); - 执行外部命令来自配置/变量lib/commands/commands/account.js:18
const child = execFile(argv[0], argv.slice(1), { timeout: 5000 }, () => { }); - 读取本地文件lib/market/commands/market.js:13
const deps = JSON.parse(readFileSync(manifest, 'utf8')).dependencies ?? {}; - 写入本地文件lib/market/commands/market.js:250
renameSync(lock, `${lock}.bak-${Date.now()}`); - 读取本地文件lib/deps/services.js:100
for (const name of readdirSync(profilesDir)) { - 读取本地文件lib/deps/services.js:102
if (!existsSync(pkgPath)) - 读取本地文件lib/deps/services.js:105
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')); - 读取本地文件lib/deps/services.js:120
for (const line of readFileSync(path, 'utf8').split('\n')) { - 读取本地文件lib/deps/services.js:177
if (existsSync(pj)) { - 读取本地文件lib/deps/services.js:179
const name = JSON.parse(readFileSync(pj, 'utf8')).name; - 执行外部命令固定命令lib/deps/services.js:385
const pnpm = spawnSync('pnpm', ['--version'], { encoding: 'utf8', timeout: 2000 }); - 写入本地文件lib/deps/services.js:471
appendFileSync(patchPath, block); - 读取本地文件lib/kernel/bridge.js:26
for (const f of readdirSync(dir).sort()) { - 写入本地文件lib/kernel/bridge.js:56
await writeFile(scratchPath, ''); - 执行外部命令固定命令lib/kernel/bridge.js:94
const child = spawn('nvim', args, { - 写入本地文件lib/kernel/bridge.js:111
appendFileSync(join(dir, 'nvim-stderr.log'), chunk); - 读取本地文件lib/kernel/bridge.js:143
tail = readFileSync(stderrLog, 'utf8').slice(-4000); - 写入本地文件lib/kernel/lifecycle.js:20
appendFileSync(app.errorLogPath, `${new Date().toISOString()} 退出诊断: ${kind} ${detail.map((d) => String(d)).join(' ')}\n`); - 执行外部命令固定命令lib/kernel/lifecycle.js:261
return spawnSync('python3', ['-c', 'pass'], { stdio: 'ignore', timeout: 3000 }).status === 0; - 执行外部命令固定命令lib/kernel/lifecycle.js:273
const next = spawn('/bin/sh', ['-c', shCmd, 'sh', argv0, ...process.argv.slice(1)], { stdio: 'inherit' }); - 发起网络请求lib/kernel/headless.js:21
const lines = await app.slices.runtime.nvim.request('nvim_buf_get_lines', [feed.bufId, 0, -1, false]); - 写入本地文件lib/kernel/headless.js:29
writeFileSync(app.dumpPath, `# dsh-nvim-tui e2e dump (${new Date().toISOString()})\n` + - 写入本地文件lib/kernel/headless.js:38
writeFileSync(app.dumpPath, `# dump failed: ${err.message}\n`); - 执行外部命令固定命令lib/kernel/term.js:21
spawnSync('python3', ['-c', 'import termios,sys; termios.tcflush(sys.stdin.fileno(), termios.TCIFLUSH)'], { stdio: 'inherit' }); - 读取本地文件lib/boot/onboarding.js:21
const parsed = JSON.parse(readFileSync(onboardFile(), 'utf8')); - 写入本地文件lib/boot/onboarding.js:31
writeFileSync(onboardFile(), JSON.stringify({ onboardedAt: new Date().toISOString() })); - 读取本地文件lib/feed/images.js:86
const raw = readFileSync(resolved); - 执行外部命令固定命令lib/feed/images.js:152
label = execFileSync('osascript', ['-e', ` - 删除本地文件lib/feed/images.js:176
unlinkSync(outPath); - 读取本地文件lib/feed/images.js:182
let raw = readFileSync(outPath); - 执行外部命令固定命令lib/feed/images.js:186
execFileSync('sips', ['-s', 'format', 'png', outPath, '--out', pngPath], { stdio: 'ignore', timeout: 5000, killSignal: 'SIGKILL' }); - 读取本地文件lib/feed/images.js:187
raw = readFileSync(pngPath); - 删除本地文件lib/feed/images.js:193
unlinkSync(pngPath); - 删除本地文件lib/feed/images.js:208
unlinkSync(outPath); - 读取本地文件lib/kernel/approval-log.js:72
return parseApprovalLines(readFileSync(path, 'utf8')).slice(-max); - 写入本地文件lib/kernel/approval-log.js:89
mkdirSync(approvalLogDir(), { recursive: true }); - 写入本地文件lib/kernel/approval-log.js:90
appendFileSync(path, approvalLine(r)); - 读取本地文件lib/kernel/approval-log.js:91
const all = parseApprovalLines(readFileSync(path, 'utf8')); - 写入本地文件lib/kernel/approval-log.js:96
appendFileSync(path + '.tmp', kept.map(approvalLine).join('')); - 写入本地文件lib/kernel/approval-log.js:97
renameSync(path + '.tmp', path); - 读取本地文件lib/kernel/session-health.js:37
names = readdirSync(dir); - 读取本地文件lib/kernel/session-health.js:50
bytes = statSync(full).size; - 读取本地文件lib/kernel/session-health.js:54
const raw = readFileSync(full); - 读取本地文件lib/commands/job-tail.js:107
const size = statSync(now.spill).size; - 读取本地文件lib/commands/job-tail.js:109
const text = readFileSync(now.spill).subarray(state.offset).toString('utf8'); - 发起网络请求lib/market/progress.js:134
const res = await fetch(url, { - 读取本地文件lib/market/progress.js:178
const raw = readFileSync(path, 'utf8'); - 写入本地文件lib/market/progress.js:189
mkdirSync(dirname(path), { recursive: true }); - 写入本地文件lib/market/progress.js:190
writeFileSync(path, JSON.stringify(catalog)); - 读取本地文件lib/market/progress.js:211
const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); - 读取本地文件lib/market/progress.js:218
const p = JSON.parse(readFileSync(join(dir, 'node_modules', name, 'package.json'), 'utf8')); - 读取本地文件lib/market/progress.js:248
return readFileSync(path, 'utf8'); - 写入本地文件lib/market/progress.js:337
mkdirSync(dirname(path), { recursive: true }); - 写入本地文件lib/market/progress.js:338
writeFileSync(path, text); - 请求外部主机registry.npmjs.orglib/market/progress.js:354
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, { - 发起网络请求lib/market/progress.js:354
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, { - 请求外部主机raw.githubusercontent.comlib/market/progress.js:391
const res = await fetch(`https://raw.githubusercontent.com/${repo}/${b}/package.json`, { - 发起网络请求lib/market/progress.js:391
const res = await fetch(`https://raw.githubusercontent.com/${repo}/${b}/package.json`, { - 请求外部主机registry.npmjs.orglib/market/progress.js:422
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(info.name)}/${info.version}`, { - 发起网络请求lib/market/progress.js:422
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(info.name)}/${info.version}`, { - 读取本地文件lib/market/progress.js:505
const manifest = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); - 读取本地文件lib/market/progress.js:507
if (existsSync(join(pkgDir, main))) - 执行外部命令固定命令lib/market/progress.js:525
execFileSync('open', [url]); - 执行外部命令固定命令lib/market/progress.js:527
execFileSync('xdg-open', [url]); - 执行外部命令固定命令lib/market/progress.js:603
const child = spawn('dsh', ['plugin', '--profile', profileName, ...args], { - 写入本地文件lib/market/progress.js:719
renameSync(lock, `${lock}.bak-${Date.now()}`);
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-llm@deepseek-ai/dsh-ptc-runtime@deepseek-ai/dsh-sandbox@deepseek-ai/dsh-sandbox-policy@deepseek-ai/dsh-scope@deepseek-ai/dsh-session@deepseek-ai/dsh-session-projection@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-tools@deepseek-ai/dsh-typert-protocol给这个插件打分还没有人投票↓
功能亮点
- Neovim风格终端界面
- 会话与工具卡片展示
Neovim 风格的 DeepSeek Harness(dsh)终端界面:nvim 负责渲染终端,Node 运行器用 msgpack-RPC 桥接 DSH 智能体事件——会话、工具卡片、推理面板、审批、markdown 表格
README
由 DeepSeek 最新模型翻译生成展开完整文档(README 剩余 1 节)
dsh-nvim-tui
用 Neovim 当渲染壳 的 DeepSeek Harness (dsh) TUI:聊天区就是普通 nvim buffer——搜索、复制、可视模式、你的 colorscheme / statusline / LSP 全部生效。Node runner 通过 msgpack-RPC 把 agent 事件流渲染进去,把输入回传给 agent。
dsh --profile tui
└─ nvim-tui-runner(Node / Cordis 插件) ├─ 订阅 session/event,注入 agents / agentDefaultModel └─ spawn nvim --listen <socket>(内置 TUI 渲染终端) └─ msgpack-RPC 双向:事件 → buffer,输入 → agent.followup
快速开始
安装到 dsh 官方 tui profile
dsh plugin --profile tui add kovey/dsh-nvim-tui
dsh --profile tui
也可以装进任意自定义 profile(如 nvim-tui),两者等价:
dsh plugin --profile nvim-tui add kovey/dsh-nvim-tui
dsh --profile nvim-tui
dsh plugin add 会把声明了 dsh.bundle 的依赖并入该 profile 的 bundles 层栈,无需手改 package.json。启动后输入 /help 查看命令,输入 / 弹出补全。
常用启动参数:--resume <id> / --continue / --model provider/model / --permission <name> / --help(见 docs/CONFIG.md)。
更新
git 依赖用 #tag 重写 ref;update / --latest 推不动 git ref
dsh plugin --profile tui add "kovey/dsh-nvim-tui#<tag>"
宿主与插件版本的对应关系、逐版本升级步骤见 UPGRADE.md。
运行依赖
| 依赖 | 要求 | 说明 |
| dsh | >=0.2.0-rc.2 <0.2.2 | 已实测 0.2.0-rc.2 / 0.2.1-rc.1 / 0.2.1-rc.2;0.2.1-rc.3 / rc.4 已验证类型面与兼容判定(rc.4 另做启动验证);0.2.2 与 0.3.0 未验证,故意不声明兼容 |
| Neovim | 0.9+(推荐 0.10+) | 0.10+ 完整体验(输入框四边框、弹窗提示嵌入边框);0.9 可运行但降级 |
| Node.js | 23.6+ | 跟随 dsh 的 engines 要求,通常无需单独安装 |
- Neovim 0.9–0.12 全系列支持:代码里只有 nvim-0.9 / nvim-0.10 两档闸门, 0.11 / 0.12 自动走完整分支;已逐项核对 0.12.6 的 breaking changes,均未使用。
- 注意预发布版:dsh 的 next dist-tag 是 release candidate,latest 可能更旧;安装时显式带版本或 @next,别让 npm i -g @deepseek-ai/dsh 装回旧版。
主要特性
- Neovim 原生体验:聊天区 / 输入窗都是普通 buffer;围栏代码块与 diff 用你的 treesitter + 配色方案高亮。
- 流式渲染:·· thinking · 12.3s 浮动活动指示;思考与工具记录收进 <C-o> 右侧面板,聊天区保持全宽。工具卡、GFM 框线表格(按显示宽度对齐、超宽自动折行)、 Claude 风格代码块。
- 转录增强:📋 待办条与 ⚙ 任务板钉在聊天区底部(实时更新,全部完成才写入转录)、 ⋯ 压缩检查点、↻ 重试状态行、workflow 嵌套回放、子代理思考链边跑边输出; PTC/code 模式的子工具调用逐条渲染,图片 offload 有提示;待办逐项更新由守卫强制(说明)。
- 文件改动:write / edit 等工具调用自动渲染 ✎ 路径 (+N −M) 统一 diff; /changes 汇总本会话改动,/diff 左右对照审阅;/files 打开 netrw 文件树, /preview 只读预览,/upload 把本地文件作为 durable 附件发送。
- 会话管理:/sessions 工作区分组浏览器、/fork 分叉、/btw 侧问、 /queue 消息队列、/search 跨会话搜索、/cwd 切换工作目录;/outline 回合大纲;启动自动续上次活跃会话(类似 claude --continue)。
- 子代理与工作流:/subagents 目录 + 对话窗(边跑边看,可续聊 continuable 子代理);/workflow 查看阶段树。
- 多模态识图:原生 image 直发;当前模型不支持图像时自动临时切到官方识图模型,回合结束切回。<C-v> 读剪贴板图片、/image 指定文件。
- 输入效率:@ 文件 / @session 引用;/ 命令补全(含宿主与插件注册的命令);精确短语走自然语言命令,模糊语句交给模型判断是否执行(tui_command 工具)。
- 模型与状态栏:模型 · effort · 缓存命中 · 上下文占用 · TTFT / tok/s · 成本 ·权限模式 · 排队 / jobs / 待办;/difficulty 按任务难度自动选模型。
- 扩展与生态:/market 插件市场(安装 / 更新 / 热启停)、/plugin 直装; /cordis 检查动态 Cordis 插件;EXT-API 允许其他 dsh 插件和 nvim 插件在 TUI 内渲染 UI、订阅会话事件。
- 可定制:/theme 预设或自定义高亮组、/density、/layout、/whale、/glance;/locale zh|en 切换界面语言。
键位
聊天区 normal 模式下 j / k、/ 搜索、n / N、gg / G、y 等原生操作都可用。运行中发送的消息会排队到当前回合结束(状态栏 ⏳ 计数,/queue 管理)。
| 键 | 作用 |
| Enter | 发送消息(输入窗聚焦时自动处于 insert 模式) |
| <C-CR> | 输入换行(窗口高度 1–6 行自适应) |
| <C-e> | 全屏输入:Enter 换行,Esc 进命令模式;命令模式下回车发送、q 丢弃 |
| ↑ / ↓ | 行尾循环输入历史(可恢复草稿) |
| / | 命令补全浮窗(Tab / <C-n> 切换,Enter 补全或执行) |
| @ | 文件 / 会话引用补全 |
| <C-v> | 剪贴板读图(macOS),随消息发送 |
| <C-c> | 停止当前回合(同 /stop) |
| <C-o> | 展开 / 收起活动面板 |
| 审批浮窗 y / a / n | 允许一次 / 总是允许 / 拒绝 |
| 提问浮窗 j k / Space / Enter | 移动 / 多选 / 确认 |
| <C-q> / :qa | 退出(:qa 直接退 nvim,runner 随之退出整个 dsh) |
| <Esc> / j k | 回到 normal 模式 / 滚动聊天区 |
| 扩展卡片 1-9 / Enter | 触发卡片动作 / 弹出动作菜单 |
斜杠命令
输入 / 弹出补全,/help 查看全部。常用命令:
| 分组 | 命令 |
| 会话 | /sessions /new /fork /btw /queue /compact /cwd /search /schedule /terminal /upload |
| 模型 | /model /models /effort /difficulty |
| 视图 | /panel /theme /density /layout /whale /locale |
| 运维 | /settings /deps /doctor /plugins /market /plugin /mcp |
| 信息 | /help /status /context /cost /changes /diff /export /outline /files /preview |
完整命令表(77 个内置命令,按分组)见 docs/COMMANDS.md。宿主与其他插件注册的命令也会进入补全目录,可用 /commands 查看。
配置
runner 行的 config: 写在 profile 的 cordis.patch.yml:
- insert:
- id: nvim-tui-runner
name: 'dsh-nvim-tui'
config:
theme:
DshTuiUser: { fg: '#7aa2f7', bold: true }
| 配置项 | 默认 | 说明 |
| loadUserConfig | true | 加载你的 nvim 配置;false 等价 -u NONE |
| theme | — | 高亮组覆盖 |
| headless | false | 无 TTY 测试模式 |
| resumeLatest | true | 启动自动续上次活跃会话 |
| locale | zh | 界面语言 zh / en |
| todoGuard | true | 待办清单逐项更新守卫 |
| difficultyRouting | — | 难度路由配置 |
完整配置项、环境变量、主题组、难度路由与 headless / CI 用法见 docs/CONFIG.md。
开发
npm install
npm run build # src/(TypeScript strict)→ lib/
npm run check # 双 tsconfig 类型检查 + 架构 / 域操作门禁
npm run smoke # 无头冒烟:RPC 往返 + Lua 插件 + 事件渲染
npm run e2e -- "你好,请只回复:收到" # 真模型回归(需 dsh 凭据)
发布用 npm publish;prepublishOnly 会依次跑 check → build → smoke。
本地仓库直链安装:在 profile 的 package.json 里把依赖写成 "dsh-nvim-tui": "link:<仓库绝对路径>",dsh.profile.bundles 包含 "dsh-nvim-tui",然后运行 dsh plugin --profile nvim-tui install,再用 dsh --profile nvim-tui 启动。
目录结构、模块分层与关键设计决策见 docs/STRUCTURE.md;架构演进与领域划分见 docs/ARCHITECTURE.md。
文档
| 文档 | 内容 |
| docs/COMMANDS.md | 斜杠命令完整参考 |
| docs/CONFIG.md | 配置项、环境变量、主题、难度路由、headless |
| docs/EXT-API.md | 插件开放接口(Node / Lua 两面) |
| docs/STRUCTURE.md | 目录结构与设计决策 |
| UPGRADE.md | 宿主与插件版本升级指南 |
| CHANGELOG.md | 版本变更记录 |
| REQUIREMENTS.md | 需求基线(M0–M7 验收状态) |
许可协议
MIT预览分享卡片长什么样 ↓
用 Neovim 当渲染壳 的 DeepSeek Harness dsh TUI:聊天区就是普通 nvim…
相关插件 · UI / 主题
这是你的仓库?认领条目