🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Neovim 终端界面 kovey/dsh-nvim-tui

DeepSeek Harness
kovey1 天前有提交已提供中文文档扫描:含敏感能力

用 Neovim 当渲染壳 的 DeepSeek Harness dsh TUI:聊天区就是普通 nvim…

33.6/100
综合分
5
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add kovey/dsh-nvim-tui
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · ui
装得上吗
作者未把包发布到 npm,只能用源码安装
插件本身没问题,是分发方式不同
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 1 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

engines.node 要求 >=23.6,不满足 Node 22.19.0

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-nvim-tui(未发布到 npm,仅可源码安装)
✗Node 引擎要求 >=23.6 · 基线 Node 22.19 不满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

源码安装时若 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.org
    lib/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.com
    lib/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.org
    lib/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()}`);
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
涉及外部主机:registry.npmjs.org、raw.githubusercontent.com

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@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 最新模型翻译生成
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
展开完整文档(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
预览分享卡片长什么样 ↓kovey/dsh-nvim-tui
分享 · DeepSeek Harness Hub
kovey/dsh-nvim-tui

用 Neovim 当渲染壳 的 DeepSeek Harness dsh TUI:聊天区就是普通 nvim…

这是你的仓库?认领条目

回到顶部