deepseek-harness-tui
An interactive terminal chat for DeepSeek Harness — terminal-native style, built with Ink (React for terminals).
Give it a TokenDance key and a dsh install; run dsh --profile tui and you get a zero-chrome terminal chat with DeepSeek models: bottom-anchored transcript, tool calls folded into cells, thinking folding, and a theme that adapts to your terminal via OSC 11. It's a thin, readable plugin (~800 lines of UI) — not a re-implementation of the harness.
English · 简体中文


Install
Requires Node.js ≥ 20 and the DeepSeek Harness CLI:
npm install -g @deepseek-ai/dsh # the harness (no Homebrew tap yet)
git clone https://github.com/gxinxing/deepseek-harness-tui
cd deepseek-harness-tui && pnpm install
Wire the plugin bundle into the tui profile (one-time):
dsh plugin --profile tui add @deepseek-ai/dsh-headless
dsh plugin --profile tui add /path/to/deepseek-harness-tui
Use
export TOKENDANCE_API_KEY=sk-... # or add it to ~/.dsh/.credentials.yaml (0600)
dsh --profile tui # open the TUI
In the TUI: ctrl + t folds the thinking trace, esc interrupts the running turn, /help shows all keys and commands.
What it does
- Terminal-native UI, not a re-skinned echo. The transcript is the surface — no boxes, no chrome. The DeepSeek brand banner (ANSI Shadow logo, gradient) greets you only on the empty state; model · cwd live in a dim footer.
- Tool calls fold into cells.
⠋ Running <cmd> while active → ✓ <cmd> • 1.2s (or ✗ on error), with output merged into the cell, dimmed, and truncated head + tail (… +N lines). No interleaved wall of raw output.
- Theme derived from your terminal. OSC 11 probes the real background: message tints and code chips are blended from it (12% white over dark, 4% black over light) — never hardcoded hex. Force a theme with
DSH_TUI_BG=#ffffff for testing.
- Thinking you can fold.
ctrl + t toggles the reasoning trace; esc aborts the turn at any time via agent.cancel({ kind: 'user' }).
- Markdown that keeps its shape. Headers keep their
#, fenced blocks keep their fences, inline code gets a subtle chip — and CJK/emoji wrap at correct character widths with an aligned gutter.
- A live viewport. The transcript is bottom-anchored; the tail is always visible. Busy state shows a braille spinner + compact elapsed timer (
Working 5s).
Learn more
Model routing (TokenDance)
The profile patch (cordis.patch.yml) routes llm-deepseek through the TokenDance gateway:
llm-deepseek:
apiKeyEnv: TOKENDANCE_API_KEY
baseURL: https://tokendance.space/gateway/v1
The provider is registered in ~/.dsh/settings.yaml (llm-pi-ai.providers.tokendance): OpenAI-compatible endpoint, thinkingFormat: deepseek, models deepseek-v4-flash (default) and deepseek-v4-pro. Switch models by editing the provider's models list or overriding llm-deepseek.model in your profile patch.
Prerequisite fix (one-time, per dsh install). TokenDance streams subsequent tool-call deltas with empty name/id; the stock @deepseek-ai/dsh-llm-deepseek adapter overwrites the first frame's call id with "" and the harness loops on unknown tool "". Apply the guard in node_modules/@deepseek-ai/dsh-llm-deepseek/lib/index.js:
- if (call.id !== void 0) block.callId = call.id
+ if (call.id) block.callId = call.id
- ... if (call.function?.name !== void 0) ...
+ ... if (call.function?.name) ...
Applied 2026-08-13 on this machine. The edit lives in the global dsh install and is lost on dsh upgrade — re-apply after upgrading (worth an upstream PR).
Self-inspection · Self-repair · Self-update loop
This project ships a complete automated quality gate — inspect → repair → update — closed loop:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Local dev │ │ Pre-commit │ │ CI / PR │
│ pnpm check │───▶│ lint-staged │───▶│ ci.yml │
│ (one-shot) │ │ (git commit)│ │ (GitHub) │
└──────────────┘ └──────────────┘ └──────────────┘
▲ │
│ ▼
│ ┌──────────────────────┐
│ │ lint + format:check │
│ │ + test (Node 20/22) │
│ └──────────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ deps.yml (auto-scan every Mon 06:00 UTC) │
│ Update found → auto PR → review & merge → closed loop │
└──────────────────────────────────────────────────────────┘
Local inspection
pnpm check # all-in-one: lint → format:check → test
pnpm lint # code quality (ESLint)
pnpm format:check # style gate (Prettier)
pnpm test # unit tests (Node built-in runner, 57 cases)
Local self-repair
pnpm lint:fix # auto-fix all fixable ESLint issues
pnpm format # auto-format all source files
On every git commit (husky + lint-staged):
- staged
*.js files → prettier --write + eslint --fix before the commit lands
- committed code is always clean — no manual
pnpm format needed
Dependency self-update
pnpm deps:check # scan all deps for available upgrades (grouped + audit)
pnpm deps:update # bump package.json to latest compatible + pnpm install
GitHub Actions auto-runs (.github/workflows/deps.yml):
- Every Monday 06:00 UTC
- Creates a
deps/auto-update-YYYYMMDD branch + PR when updates exist
- Manual trigger available from the GitHub Actions tab
CI gate (.github/workflows/ci.yml)
| Trigger |
Job |
Matrix |
push / pull_request to main |
inspect |
Node 20 + Node 22 |
|
lint |
✅ |
|
format:check |
✅ |
|
test (57 cases) |
✅ |
|
coverage upload |
Node 22 only |
Any stage failure blocks the merge — main is always green.
Contributing
Issues and PRs are welcome. Good first tasks: upstream the two runtime patches (TokenDance tool-call guard, grep permission-error tolerance), add a screenshot for light themes, or port the welcome banner to other model providers. See INTEGRATION-NOTES.md before touching the event bridge.
License
MIT. An independent community project, not affiliated with DeepSeek or TokenDance.