Skip to content

Commit 8fd5227

Browse files
authored
feat: rename DeepSeek TUI agent to CodeWhale (#87)
* feat(cli): add CLI tool with spinner progress and auto-save HTML - 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 * fix(cli): fail hard on non-HTML output, flush status in non-TTY mode - 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 * fix(cli): robust error handling, multi-file support, overwrite prompt 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. * fix(cli): pre-scan batch outputs for basename collisions 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. * fix(cli): non-TTY batch overwrite + collision output path safety - 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. * fix(cli): deduplicate aider/deepseek close output + reject unsupported 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. * Revert "fix(cli): deduplicate aider/deepseek close output + reject unsupported default agents" This reverts commit 19636bc. * fix(cli): deduplicate aider/deepseek output + reject unsupported 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 <!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. * fix(cli): resolve *_BIN env overrides via PATH for detection 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. * fix: auto-enable relative paths for basename collisions in non-TTY mode * feat(cli): add comprehensive test framework (89 tests across 6 suites) 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. * fix(cli): resolve relative *_BIN overrides in tryPath 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. * test(cli): add relative *_BIN override resolution tests 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. * feat(cli): add auto command with intelligent template matching Implements automatic template detection for the CLI, partially resolves #60 and supplements the CLI entrypoint introduced in #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 * fix(cli): word-boundary keyword matching, force-ai gating, EPIPE guard - 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) * fix(cli): address PR #80 review feedback + add skills-matcher 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 * feat: rename DeepSeek TUI agent to CodeWhale 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 * fix(next): deduplicate aide/codewhale/deepseek-tui close-path delta 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.
1 parent b54a0fb commit 8fd5227

30 files changed

Lines changed: 4989 additions & 16 deletions

cli/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
node_modules/

cli/README.md

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# html-anything CLI
2+
3+
从命令行直接将 Markdown(或纯文本/CSV/JSON)转换为精美排版的 HTML 文件,无需打开网页界面。
4+
5+
## 安装
6+
7+
```bash
8+
# 1. 进入项目目录,安装依赖
9+
cd html-anything
10+
pnpm install
11+
12+
# 2. 构建 CLI
13+
pnpm -F @html-anything/cli build
14+
```
15+
16+
### 全局安装(可选)
17+
18+
构建完成后,可以创建全局链接:
19+
20+
```bash
21+
# 在 cli 目录下创建全局链接
22+
cd cli
23+
npm link
24+
25+
# 之后可以在任意目录使用
26+
html-anything --help
27+
```
28+
29+
或者将以下别名添加到 `~/.zshrc``~/.bashrc`
30+
31+
```bash
32+
alias html-anything="node /path/to/html-anything/cli/dist/run.js"
33+
```
34+
35+
## 快速开始
36+
37+
### 1. 设置默认模板(推荐)
38+
39+
```bash
40+
# 查看所有可用模板
41+
html-anything templates
42+
43+
# 设置一个默认模板,之后 convert 时无需每次都指定
44+
html-anything config set-default-template doc-kami-parchment
45+
```
46+
47+
### 2. 转换 Markdown 文件
48+
49+
```bash
50+
# 自动匹配模板(推荐:无需手动选模板)
51+
html-anything auto article.md
52+
53+
# 仅查看匹配结果,不执行转换
54+
html-anything auto article.md --show-match-only
55+
56+
# 使用默认模板转换(自动保存为 article.html)
57+
html-anything convert article.md
58+
59+
# 批量转换多个文件
60+
html-anything convert file1.md file2.md file3.md -d ./dist
61+
62+
# 保存到指定文件
63+
html-anything convert article.md -o output.html
64+
65+
# 指定自动保存目录
66+
html-anything convert article.md -d ./dist
67+
68+
# 指定模板
69+
html-anything convert article.md -t resume-modern
70+
71+
# 指定 AI agent
72+
html-anything convert article.md -a claude --model sonnet
73+
74+
# 从标准输入读取(输出到 stdout)
75+
cat article.md | html-anything convert -o page.html
76+
```
77+
78+
### 3. 查看生成结果
79+
80+
```bash
81+
# 用浏览器打开生成的 HTML
82+
open output.html
83+
```
84+
85+
## 命令详解
86+
87+
### `convert` / `auto` — 转换内容
88+
89+
两个命令共享以下通用参数:
90+
91+
| 参数 | 简写 | 说明 | 默认值 |
92+
|------|------|------|--------|
93+
| `input` || 输入文件路径,省略则从 stdin 读取 | stdin |
94+
| `--agent <id>` | `-a` | AI agent ID | 自动检测第一个可用 agent |
95+
| `--output <path>` | `-o` | 输出文件路径 | 自动保存为 `<输入文件名>.html`,stdin 输入时输出到 stdout |
96+
| `--output-dir <dir>` | `-d` | 自动保存目录 | 当前目录 |
97+
| `--model <id>` || 使用的模型 | agent 默认模型 |
98+
| `--format <type>` || 输入格式:markdown, text, csv, json | markdown |
99+
100+
#### `convert` — 指定模板转换
101+
102+
```bash
103+
html-anything convert [input] [options]
104+
```
105+
106+
用户明确指定模板 ID 来转换内容。
107+
108+
| 参数 | 简写 | 说明 | 默认值 |
109+
|------|------|------|--------|
110+
| `--template <id>` | `-t` | 模板 ID | 配置中的 default-template |
111+
112+
#### `auto` — 自动匹配模板并转换
113+
114+
```bash
115+
html-anything auto [input] [options]
116+
```
117+
118+
无需手动选择模板,CLI 自动分析内容主题,从 75 个模板中匹配最合适的模板,然后执行转换。
119+
120+
| 参数 | 简写 | 说明 | 默认值 |
121+
|------|------|------|--------|
122+
| `--force-ai` || 跳过关键词匹配,强制使用 AI summary ||
123+
| `--show-match-only` || 仅显示匹配结果,不执行转换 ||
124+
125+
### `templates` — 列出模板
126+
127+
```bash
128+
html-anything templates
129+
```
130+
131+
列出所有 75 个可用模板,按类别分组显示。已设为默认的模板会标记 `(default)`
132+
133+
### `agents` — 列出 Agent
134+
135+
```bash
136+
html-anything agents
137+
```
138+
139+
列出系统中已安装的 AI agent CLI。`` 表示可用,`` 表示未安装。
140+
141+
### `config` — 配置管理
142+
143+
```bash
144+
html-anything config # 查看当前配置
145+
html-anything config set-default-template <id> # 设置默认模板
146+
html-anything config set-default-agent <id> # 设置默认 agent
147+
html-anything config set-model <id> # 设置默认模型
148+
html-anything config reset # 重置所有配置
149+
```
150+
151+
配置文件位于 `~/.config/html-anything/config.json`
152+
153+
## 支持的 AI Agent
154+
155+
html-anything 本身不做 AI 生成,它会自动检测并调用你系统里已安装的 AI CLI 工具(任意一个即可)来完成转换。支持的 AI CLI:
156+
157+
| Agent | 安装方式 |
158+
|-------|----------|
159+
| Claude Code | `npm install -g @anthropic-ai/claude-code` |
160+
| OpenAI Codex | `npm install -g @openai/codex` |
161+
| Cursor Agent | 安装 Cursor 编辑器后可用 |
162+
| Gemini CLI | `npm install -g @google/gemini-cli` |
163+
| GitHub Copilot CLI | `npm install -g @github/copilot-cli` |
164+
| OpenCode | `npm install -g @open/open-cli` |
165+
| Qwen Coder | `npm install -g @alibaba/qwen-coder` |
166+
| CodeWhale | `npm install -g codewhale` |
167+
| DeepSeek TUI | `npm install -g deepseek-tui` |
168+
| Aider | `pip install aider-chat` |
169+
| OpenClaw | 参考官方文档安装 |
170+
171+
## 常用模板推荐
172+
173+
| 模板 ID | 名称 | 适用场景 |
174+
|---------|------|----------|
175+
| `doc-kami-parchment` | Kami 羊皮纸文档 | 长文、报告、one-pager |
176+
| `resume-modern` | 极简简历 | A4 单页简历 |
177+
| `deck-swiss-international` | 瑞士国际主义 Deck | 演示文稿 |
178+
| `deck-guizang-editorial` | 贵赞编辑墨水 Deck | 杂志风 PPT |
179+
| `magazine-poster` | 杂志风海报 | 海报、宣传单页 |
180+
| `blog-post` | 博客长文 | 技术博客 |
181+
| `data-report` | 数据可视化报告 | 数据分析报告 |
182+
| `card-xiaohongshu` | 小红书图文卡片 | 社交媒体图文 |
183+
| `prototype-web` | Web 产品原型 | 产品原型 |
184+
| `saas-landing` | SaaS Landing | 产品落地页 |
185+
186+
## 完整使用示例
187+
188+
```bash
189+
# 1. 首次使用:查看有哪些模板
190+
html-anything templates
191+
192+
# 2. 设置你最喜欢的模板为默认
193+
html-anything config set-default-template doc-kami-parchment
194+
195+
# 3. 写一篇 Markdown 文章
196+
cat > my-article.md << 'EOF'
197+
# 我的项目总结
198+
199+
## 背景
200+
这是一个关于...
201+
202+
## 成果
203+
- 完成了 A 功能
204+
- 优化了 B 模块
205+
206+
## 下一步
207+
我们计划在 Q3 完成...
208+
EOF
209+
210+
# 4. 一键自动匹配模板并转换(推荐)
211+
html-anything auto my-article.md
212+
213+
# 5. 在浏览器中查看结果
214+
open my-article.html
215+
216+
# 6. 如果只想看匹配结果
217+
html-anything auto my-article.md --show-match-only
218+
219+
# 7. 如果想换个风格
220+
html-anything convert my-article.md -t blog-post -o my-article-v2.html
221+
222+
# 8. 保存到指定目录
223+
html-anything convert my-article.md -d ./output
224+
```
225+
226+
## 开发
227+
228+
```bash
229+
# 开发模式(无需构建,直接运行 TypeScript)
230+
pnpm -F @html-anything/cli dev -- templates
231+
232+
# 类型检查
233+
pnpm -F @html-anything/cli typecheck
234+
235+
# 构建
236+
pnpm -F @html-anything/cli build
237+
```
238+
239+
## 工作原理
240+
241+
### `convert` 命令流程
242+
243+
1. **模板加载**:从 `next/src/lib/templates/skills/` 加载 75 个 SKILL 模板,每个模板包含视觉风格定义和排版规则
244+
2. **Prompt 拼接**:将全局设计指令 + 模板专属规则 + 用户内容拼接成一个完整的 AI prompt
245+
3. **Agent 调用**:调用本地安装的 AI agent CLI(如 Claude Code),让 AI 根据 prompt 生成 HTML
246+
4. **HTML 提取**:从 agent 的流式输出中提取完整的 HTML 文档
247+
5. **输出**:将 HTML 写入文件或打印到 stdout
248+
249+
### `auto` 命令流程
250+
251+
```
252+
用户内容
253+
254+
255+
┌─────────────────────────┐
256+
│ 第一层:强信号关键词匹配 │ ← 零 token,毫秒级
257+
│ 命中 → 直接使用匹配模板 │
258+
│ (简历→resume-modern 等) │
259+
└──────────┬──────────────┘
260+
│ 未命中
261+
262+
┌─────────────────────────┐
263+
│ 第二层:规则打分匹配 │ ← 零 token,毫秒级
264+
│ 内容 × 全部模板 metadata │
265+
│ (tags + name + desc + │
266+
│ scenario keywords) │
267+
│ 置信度 ≥ 阈值 → 使用 │
268+
└──────────┬──────────────┘
269+
│ 置信度不足
270+
271+
┌─────────────────────────┐
272+
│ 第三层:AI Summary 兜底 │ ← 仅在规则失配时
273+
│ 提取内容前 800 字 │
274+
│ → AI 判断主题类型 │
275+
│ → 再次规则匹配 │
276+
└──────────┬──────────────┘
277+
278+
279+
执行转换
280+
```
281+
282+
**匹配策略说明**
283+
- **强信号**~80 条规则):覆盖简历、定价、OKR、PRD、周报等高频场景,命中即定
284+
- **规则打分**:遍历所有模板的 tags、名称、描述、场景关键词,累加得分
285+
- **AI 兜底**:内容 ≥ 60 字且前两层均低置信度时,调用 AI 做一句话主题摘要,仅消耗极少量 token
286+
- **最终兜底**:若所有层均失败,回退到 `deck-swiss-international` 通用模板
287+
288+
整个过程完全本地运行,不依赖任何外部 API key,使用你已有的 agent 订阅。转换过程中会显示动画进度指示器,展示已接收的文本块数和耗时。

cli/package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@html-anything/cli",
3+
"version": "0.1.0",
4+
"private": true,
5+
"license": "Apache-2.0",
6+
"type": "module",
7+
"bin": {
8+
"html-anything": "./dist/run.js"
9+
},
10+
"scripts": {
11+
"build": "tsc",
12+
"dev": "tsx src/index.ts",
13+
"typecheck": "tsc --noEmit",
14+
"test": "vitest run",
15+
"test:watch": "vitest"
16+
},
17+
"dependencies": {},
18+
"devDependencies": {
19+
"@types/node": "^20",
20+
"tsx": "^4.22.1",
21+
"typescript": "^5",
22+
"vitest": "^4.1.6"
23+
}
24+
}

0 commit comments

Comments
 (0)