feat: rename DeepSeek TUI agent to CodeWhale#87
Conversation
- Add CLI package that converts Markdown to styled HTML via local AI agents - Support 8 coding-agent CLIs (Claude Code, Codex, Cursor Agent, Gemini, etc.) - 75 skill templates from next/src/lib/templates/skills/ - Spinner progress indicator with chunk count and elapsed time (zero deps, pure ANSI) - Auto-save output to <input>.html when input is a file - --output-dir / -d flag to specify auto-save directory - Config management (default template, agent, model) - Stdin support for piping content Part of: nexu-io/html-anything
- extractHtml: return empty string instead of wrapping non-HTML in pre tag, so the CLI correctly surfaces agent errors (rate limits, auth failures) instead of silently saving a valid-looking HTML file around error text - createSpinner: in the non-TTY branch, still flush the final status message to stderr so CI/piped scripts can diagnose failures
Agent exit-code & stderr (A): track done.code and stderr; if the agent exits non-zero, report the failure instead of silently saving a (possibly truncated) HTML file with exit 0. Format validation (B): reject unknown --format values with a list of supported formats (markdown, text, csv, json). Config write guard (C): catch filesystem errors in saveConfig() so disk- full/permission failures show a readable message instead of an uncaught exception. Overwrite prompt (D): ask before overwriting an existing output file in TTY mode; skip the prompt (auto-overwrite) when piped/CI. EPIPE handler (E): catch broken-pipe errors on stdout so piping to head(1) or early-closing consumers does not print a noisy stacktrace. -o/-d conflict (F): error when both --output and --output-dir are set. Multi-file support (G): accept multiple positional input files, process each sequentially, then summarise failures.
When multiple input files would produce the same output basename (e.g. dir1/readme.md and dir2/readme.md both -> readme.html), the CLI now pre-scans before any work begins: 1. Collision detection — lists conflicting basenames and asks whether to resolve by preserving relative directory paths (dir1/readme.html). 2. Overwrite check — after resolving all output paths, checks whether any target files already exist and asks for confirmation before overwriting. 3. On N at any step, the CLI aborts with a clear error before any agent work starts.
- Batch overwrite now skips the interactive prompt outside TTY (matching the single-file promptOverwrite auto-overwrite behaviour), so scripted CI runs don't abort when existing outputs are present. - resolveCollisionOutput now derives relative paths from the common ancestor of all colliding inputs (findCommonPath) instead of cwd, and strips '..' segments so outputs stay inside --output-dir, even when inputs live outside the current working directory.
…d default agents - agents-invoke: aider/deepseek close path now enqueues stdoutBuf directly instead of running it through both parse() AND a raw enqueue, which was producing duplicate HTML (two <!DOCTYPE html> blocks). - handleConfig set-default-agent: now rejects agents that are not installed (!available) or use an unsupported protocol (unsupported), with a clear error listing available supported alternatives. - findAgent: when resolving config.defaultAgent, now also filters out unsupported agents so a stale default (e.g. from manual config.json edit) automatically falls through to the next available agent.
…supported default agents" This reverts commit 19636bc.
…ult agents - agents-invoke: aider/deepseek close path now enqueues stdoutBuf directly instead of running it through both parse() AND a raw enqueue, which was producing duplicate <!DOCTYPE html> blocks. - findAgent: when resolving config.defaultAgent, now also filters out unsupported agents so a stale default (e.g. from manual config.json edit) automatically falls through to the next available agent. - handleConfig set-default-agent: now rejects agents that are not installed or use an unsupported protocol, with a clear error listing available supported alternatives.
detectAgents() previously only accepted *_BIN overrides as absolute paths (existsSync). Relative command names like GEMINI_BIN=fake-claude were dropped even though invocation (resolveBinForAgent) can find them on PATH. Now falls back to resolveOnPath() when existsSync fails, so detection and config flows match the actual invoke behaviour.
Based on all reviewer feedback across 10 rounds, added a complete regression test suite covering every reported failure path: - extract-html.test.ts (9): non-HTML content returns empty, no scaffold wrapping - prompt.test.ts (11): TTY/non-TTY behavior for promptYesNo & promptOverwrite - collision-resolve.test.ts (8): findCommonPath & resolveCollisionOutput edge cases - agents-detect.test.ts (20): *_BIN env overrides, PATH resolution, unsupported protocols - agents-invoke.test.ts (19): DeepSeek/Aider close path no double-enqueue, exit code propagation - index.test.ts (22): param validation, config set-default-agent guards, convert integration Refactored for testability: - Extracted collision-resolve.ts (findCommonPath + resolveCollisionOutput) - Extracted prompt.ts (promptYesNo + promptOverwrite) All 89 tests pass. Typecheck and build clean.
tryPath() in resolveBinForAgent previously only handled absolute paths (starting with / or C:\) and command names on PATH. Relative paths like ./mock-deepseek or ../wrappers/claude fell through to resolveOnPath() which only searches PATH directories, causing a mismatch where detectAgents() reported the agent as available but invokeAgent() could not find it. Now paths containing / or \ or starting with . are resolved via path.resolve() + existsSync(), matching what detectAgents() does.
Two new test cases verify that invokeAgent correctly resolves relative binOverride paths (e.g. ./mock-agent, ../bin/claude) via path.resolve() + existsSync(), matching what detectAgents() already does.
Implements automatic template detection for the CLI, partially resolves nexu-io#60 and supplements the CLI entrypoint introduced in nexu-io#75. - Add skills-matcher.ts with three-layer matching strategy: 1. ~80 strong-signal keyword rules (resume→resume-modern, etc.) 2. Full-template scoring (tags + name + description + scenario) 3. AI summary fallback only when confidence is low (~0 tokens) - Add `auto` command: html-anything auto article.md - Support --force-ai (skip rules) and --show-match-only flags - Update README with consolidated parameter docs and decision flowchart Examples: html-anything auto resume.md # auto-match + convert html-anything auto article.md --show-match-only # preview match only
- Add kwMatches() with \b word-boundary for ASCII keywords, substring for CJK - Remove ambiguous short keywords: "X", "RED", "TODO", "done", "doing", "todo" - Gate Layer-2 fallback on !forceAi so --force-ai reaches AI summary - Add EPIPE guard to handleAuto stdin-to-stdout path (matching handleConvert)
…tests - Add kwMatches() with \b word-boundary for ASCII keywords, substring for CJK - Remove ambiguous short keywords: "X", "RED", "TODO", "done", "doing", "todo" - Gate Layer-2 fallback on !forceAi so --force-ai reaches AI summary - Add EPIPE guard to handleAuto stdin-to-stdout path - Fix Layer-1 gate: strong-signal matching now works for any content length - Export kwMatches for unit testing - Add skills-matcher.test.ts with 39 tests covering kwMatches, strong-signal matching, false-positive prevention, --force-ai path, fallback, and reason output
Background: deepseek-tui has been officially renamed to CodeWhale (see https://github.com/Hmbown/CodeWhale/releases/tag/v0.8.41). The legacy deepseek and deepseek-tui binaries are deprecation shims that will be removed in v0.9.0. Changes: - Add new AgentDef 'codewhale' (bin: codewhale, vendor: CodeWhale) - Rename 'deepseek' AgentDef id to 'deepseek-tui' (bin: deepseek-tui) - Both entries use mutual fallbackBins so GUI detects either binary - Add codewhale branch to buildArgv, parseLineWithState, close-path - Update error messages to list both codewhale and deepseek-tui - Update GUI (settings-modal, welcome-modal) with CodeWhale vendor - Update README with both CodeWhale and DeepSeek TUI rows - Reserve 'deepseek' id for future official DeepSeek agent - Add test coverage for both codewhale and deepseek-tui agents - 133 tests pass, typecheck clean
PerishCode
left a comment
There was a problem hiding this comment.
@chenmofei thanks for the rename — splitting codewhale and deepseek-tui into two AgentDef entries with mutual fallbackBins is a clean way to ride out the v0.8.x deprecation cycle, and the GUI/CLI surfaces (vendor gradients, install hints, error-message agent lists, exec --auto argv) line up consistently across both packages. One non-blocking correctness finding on the Next-side close-path is noted inline; the equivalent fix already lives in the CLI sibling.
🔁 Powered by Looper · runner=reviewer · agent=claude-code · An autonomous AI dev team for your GitHub repos.
| else if (part.kind === "meta") safeEnqueue({ type: "meta", key: part.key, value: part.value }); | ||
| } | ||
| if (opts.agent === "aider" || opts.agent === "deepseek") { | ||
| if (opts.agent === "aider" || opts.agent === "codewhale" || opts.agent === "deepseek-tui") { |
There was a problem hiding this comment.
Duplicate close-path delta for codewhale / deepseek-tui (and pre-existing aider).
The parse(stdoutBuf) loop above already emits the residual buffer as a delta for these three agents — the parser at next/src/lib/agents/argv.ts:235 returns [{ kind: "delta", text: trimmed + "\n" }] whenever the agent is aider | codewhale | deepseek-tui. This added safeEnqueue({ type: "delta", text: stdoutBuf }) then sends the same residual text a second time, so any final stdout chunk that doesn't end on a newline (the typical end-of-stream shape for deepseek-tui / codewhale running exec --auto) is delivered twice.
Why it matters: downstream consumers concatenate every delta into the rendered/saved HTML, so the duplicated tail — often a chunk of the actual document including a </html> closer — leaks into the preview and any persisted/exported file. Before this PR the buggy path covered one agent (deepseek); after the rename it covers three (aider, codewhale, deepseek-tui), so the regression footprint grows even though the bug itself pre-existed.
Evidence the fix is missing only on the Next side: the CLI sibling cli/src/agents-invoke.ts:595-604 uses if/else for exactly this case (commit 889878e — "fix(cli): deduplicate aider/deepseek output …"), and cli/src/__tests__/agents-invoke.test.ts:489-507 asserts expect(deltas).toHaveLength(1) for codewhale close-path with a no-trailing-newline payload. There is no equivalent test on the Next side, but the dispatch code is the only structural difference.
Suggested change: mirror the CLI structure so the raw enqueue replaces (rather than stacks onto) the parser dispatch for these three agents:
} else if (stdoutBuf) {
if (opts.agent === "aider" || opts.agent === "codewhale" || opts.agent === "deepseek-tui") {
safeEnqueue({ type: "delta", text: stdoutBuf });
} else {
for (const part of parse(stdoutBuf)) {
if (part.kind === "delta") safeEnqueue({ type: "delta", text: part.text });
else if (part.kind === "html") safeEnqueue({ type: "html", text: part.text });
else if (part.kind === "meta") safeEnqueue({ type: "meta", key: part.key, value: part.value });
}
}
}There was a problem hiding this comment.
Fixed in 71204db — mirrored the CLI's if/else structure.
Mirror the CLI's if/else structure so the raw enqueue replaces (rather than stacks onto) the parser dispatch. Previously, the parser would emit the delta first, then the raw enqueue would emit a duplicate — causing doubled tail output in the preview.
|
Good catch! Fixed in 71204db — mirrored the CLI's if/else structure so the raw enqueue replaces (rather than stacks onto) the parser dispatch for aider/codewhale/deepseek-tui. Typecheck & tests still green. |
PerishCode
left a comment
There was a problem hiding this comment.
@chenmofei thanks for the quick turn-around on the close-path fix in 71204db — the if/else now mirrors the CLI sibling exactly, so the parser dispatch and the raw stdoutBuf enqueue are mutually exclusive for aider / codewhale / deepseek-tui and the duplicate-tail regression is gone. Spot-checked the rest of the rename surface (mutual fallbackBins in detect.ts, the new codewhale branches in buildArgv / parseLineWithState, the VENDOR_GRADIENT + VENDOR_HINT entries, and the updated agent list in UnsupportedAgentProtocolError) and everything lines up consistently. Nice clean follow-up.
🔁 Powered by Looper · runner=reviewer · agent=claude-code · An autonomous AI dev team for your GitHub repos.
|
Quick status update: @PerishCode has approved the current head, and I don’t see any author action needed right now. @Eli-tangerine, when you have a chance, could you take the product pass on the user-visible agent rename / CLI ID change? Once product review and checks are in, this can move toward merge. |
@Eli-tangerine 这个用户好像不存在了?我看账号页面提示 404 |
|
你说得对,我这边也确认这个 GitHub 用户页目前是 404。这个不是你需要处理的问题;我会把产品 review 改走内部通道,并让维护侧确认正确的公开 reviewer 配置。 当前代码侧 @PerishCode 已经 approve,后续主要还差产品确认和检查状态。谢谢提醒 🙏 |
Background
deepseek-tui 已正式更名为 CodeWhale(v0.8.41, 2026-05-23)。旧版
deepseek和deepseek-tui二进制将作为废弃兼容 shim 保留一个发布周期,v0.9.0 移除。为什么要改
deepseek、bin 为deepseek,已安装 CodeWhale 的用户无法被识别deepseek命名空间冲突:agent iddeepseek与未来 DeepSeek 官方的 Code Agent 可能产生冲突,本次完整释放,预留给官方Changes
两条独立 AgentDef,互相 fallbackBins 检测
安装任一二进制,GUI 两个卡片均显示为可用:
codewhaledeepseek-tuiCodeWhaleDeepSeek TUIcodewhaledeepseek-tuideepseek-tuicodewhaleCODEWHALE_BINDEEPSEEK_TUI_BINCodeWhaleDeepSeekargvargv调用逻辑
buildArgv / parseLineWithState / close-path 均新增 codewhale 分支,共享 exec --auto 调用。
命名空间释放
deepseek不再作为 agent id、bin 或标识符出现,完整留给未来 DeepSeek 官方 Code Agent。Behavior
GUI
CLI
-a codewhale-a deepseek-tui-a deepseekFiles Changed
Test Results
7 files / 133 tests passed / CLI typecheck OK / Next typecheck OK