README.md

# cmdc_tui

基于 [ExRatatui](https://hex.pm/packages/ex_ratatui) 的 [CMDC](https://hex.pm/packages/cmdc) AI Agent 终端交互界面(TUI)。

在终端内与 AI Agent 实时对话,实时查看工具调用过程、Todo 列表、Token 用量。

```
┌─────────────────────────────────┬──────────────────┐
│                                 │  Tools [Tab]     │
│  对话 (3)                        │  ──────────────  │
│  ◆ AI                           │  read_file  done │
│  你好!需要帮忙吗?               │  shell  running  │
│                                 ├──────────────────┤
│  ▶ You                          │                  │
│  帮我列出当前目录的文件            │                  │
│                                 │                  │
│─────────────────────────────────┤                  │
│ 输入 ────────────────────────── │                  │
│ 输入消息... (Ctrl+S 发送)         │                  │
└─────────────────────────────────┴──────────────────┘
│ idle │ session: live │ MiniMax-M2.7 │ Ctrl+S:发送   │
└──────────────────────────────────────────────────────┘
```

---

## 安装

在 `mix.exs` 中添加依赖:

```elixir
def deps do
  [
    {:cmdc_tui, "~> 0.1"}
  ]
end
```

---

## 快速启动(命令行)

`cmdc_tui` 内置 `mix tui` 任务,通过环境变量配置后直接运行:

```bash
# 无 Agent(纯输入测试)
mix tui

# 带 Agent(无工具)
LLM_API_KEY="sk-xxx" \
LLM_BASE_URL="https://api.minimaxi.com/anthropic" \
mix tui

# 带 Agent + 工具(推荐)
LLM_API_KEY="sk-xxx" \
LLM_BASE_URL="https://api.minimaxi.com/anthropic" \
LLM_TOOLS="shell,read_file,write_file,list_dir,glob,grep" \
LLM_WORKING_DIR="/your/project" \
mix tui
```

### 环境变量说明

| 变量 | 必填 | 说明 | 默认值 |
|------|------|------|--------|
| `LLM_API_KEY` | 连接 Agent 时必填 | LLM API 密钥 | — |
| `LLM_BASE_URL` | 否 | API 地址(自定义端点时填写) | Anthropic 官方 |
| `LLM_MODEL_ID` | 否 | 模型 ID | `MiniMax-M2.7` |
| `LLM_SYSTEM_PROMPT` | 否 | 系统提示词 | `你是一个 AI 助手。` |
| `LLM_TOOLS` | 否 | 启用工具,逗号分隔 | 无 |
| `LLM_WORKING_DIR` | 否 | Agent 工作目录(文件工具的根路径) | 当前目录 |

### 可用工具

| 工具名 | 功能 |
|--------|------|
| `shell` | 执行 shell 命令 |
| `read_file` | 读取文件 |
| `write_file` | 写入文件 |
| `edit_file` | 编辑文件(字符串替换) |
| `list_dir` | 列出目录 |
| `glob` | 文件模式匹配 |
| `grep` | 搜索文件内容 |
| `ask_user` | Agent 向用户提问(触发弹窗) |
| `write_todos` | Agent 写入 Todo 列表 |
| `compact_conversation` | 压缩对话历史 |
| `task` | 创建子任务 |

---

## 代码集成

如果需要在 Elixir 代码中嵌入 TUI:

```elixir
# 最简集成
{:ok, session} = CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  tools: [CMDC.Tool.Shell, CMDC.Tool.ReadFile],
  provider_opts: [api_key: System.get_env("ANTHROPIC_API_KEY")],
  working_dir: "/my/project"
)

CmdcTui.start(session: session)

# 也可以不传 session,仅用作本地输入测试
CmdcTui.start()
```

### `CmdcTui.start/1` 参数

| 参数 | 类型 | 说明 |
|------|------|------|
| `:session` | `pid` | CMDC Agent session pid |
| `:session_id` | `String.t()` | 会话 ID(显示在状态栏) |
| `:model_name` | `String.t()` | 模型名称(显示在状态栏) |

---

## 界面说明

### 布局

```
┌──────────────────────────┬─────────────────┐
│      ChatPanel            │  侧栏 (Tools /  │
│  (消息列表 + 输入框)       │     Todo)       │
├──────────────────────────┴─────────────────┤
│              StatusBar                      │
└─────────────────────────────────────────────┘
```

> 侧栏在终端宽度 **≥ 80 列**时自动显示。

### ChatPanel

- **◆ AI** — AI 回复,自动检测并渲染 Markdown(代码块、标题、列表等)
- **▶ You** — 用户消息
- **⚙ tool_name** — 工具执行结果摘要
- **AI is thinking...** — Agent 思考中的动态 spinner
- **◆ AI (streaming…)** — 流式输出进行中

### Tools 面板(侧栏)

按 `Tab` 切换到 Tools 面板,显示每次工具调用的:

| 列 | 内容 |
|----|------|
| Name | 工具名称 |
| Args | 参数摘要(前 30 字符) |
| Status | `⏳ running` / `✓ done` / `✗ error` / `⊘ blocked` |
| Time | 耗时(ms 或 s) |

### Todo 面板(侧栏)

按 `Tab` 切换到 Todo 面板,显示 Agent 的任务列表(需 Agent 调用 `write_todos` 工具):

- ☐ 未开始(白色)
- **▶ 进行中**(黄色加粗)
- ✓ 已完成(绿色)
- ✗ 已取消(灰色)

### StatusBar

底部状态栏从左到右依次显示:

```
│ idle │ session: live │ MiniMax-M2.7 │ in: 1024 / out: 256 │ $0.001 │ Enter:发送 ...
```

---

## 快捷键

### 全局

| 快捷键 | 功能 |
|--------|------|
| `Enter` | 发送消息 |
| `Shift+Enter` | 换行(多行输入) |
| `Ctrl+S` | 发送消息(备用) |
| `Ctrl+P` | 打开命令面板 |
| `Tab` | 切换侧栏(Tools ↔ Todo) |
| `↑ / ↓` | 滚动消息历史 |
| `PgUp / PgDn` | 快速滚动(每次 5 条) |
| `Ctrl+C` | 退出 TUI |

### 命令面板(Ctrl+P)

| 操作 | 功能 |
|------|------|
| 输入文字 | 过滤命令(前缀匹配) |
| `↑ / ↓` | 上下选择 |
| `Enter` | 执行选中命令 |
| `Esc` | 关闭面板 |

### Slash 命令(输入框)

在输入框中输入 `/` 触发内联自动补全,继续输入过滤:

| 命令 | 别名 | 功能 |
|------|------|------|
| `/help` | `/h`, `/?` | 显示帮助 |
| `/clear` | `/cls` | 清空对话历史 |
| `/model` | `/m` | 查看当前模型 |
| `/compact` | — | 压缩对话历史 |
| `/stop` | `/abort`, `/cancel` | 终止 Agent |
| `/tools` | — | 切换到 Tools 面板 |
| `/todos` | — | 切换到 Todo 面板 |
| `/quit` | `/exit`, `/q` | 退出 TUI |

### HITL 审批弹窗(ApprovalDialog)

当 Agent 请求操作审批时弹出:

| 按键 | 功能 |
|------|------|
| `y` | 批准 |
| `n` | 拒绝 |
| `Esc` | 拒绝并关闭 |

### Agent 提问弹窗(AskUserDialog)

当 Agent 调用 `ask_user` 工具时弹出:

| 按键 | 功能 |
|------|------|
| 输入文字 | 回答内容 |
| `1`~`9` | 快速选择选项(有选项时) |
| `Enter` | 提交回答 |
| `Esc` | 取消(提交空字符串) |

---

## 要求

- Elixir `~> 1.17`
- [CMDC](https://hex.pm/packages/cmdc) `~> 0.1`
- [ExRatatui](https://hex.pm/packages/ex_ratatui) `~> 0.5.1`(含预编译 NIF,无需 Rust 工具链)
- 终端支持 256 色 / TrueColor(推荐)
- 终端宽度 **≥ 80 列**可显示侧栏

---

## 常见问题

**Q: 侧栏没有显示?**

终端宽度不足 80 列时侧栏自动隐藏。请把终端窗口拉宽后重启 TUI。

**Q: 工具面板没有内容?**

需要在创建 Agent 时配置 `tools` 参数,或在 `mix tui` 时设置 `LLM_TOOLS` 环境变量。Agent 调用工具后侧栏才会有数据。

**Q: 输入中文有延迟?**

请使用 `mix tui` 直接启动,不要用 `iex -S mix`。`iex` 的 IO 系统会与 TUI 争抢终端控制权,导致输入延迟。

**Q: Shift+Enter 无法换行(跟 Enter 一样发送了)?**

你的终端不支持 Kitty keyboard protocol(CSI u)。TUI 启动时会自动尝试启用该协议,但部分终端不支持:

- **macOS Terminal.app** — 不支持,Enter 和 Shift+Enter 无法区分,请用 `Ctrl+S` 发送
- **iTerm2** — 需手动开启:Preferences → Profiles → Keys → 勾选 "Report modifiers using CSI u"
- **WezTerm / Kitty** — 原生支持,无需配置

如果 Shift+Enter 始终无法换行,可以用 `Ctrl+S` 作为备用发送键。

**Q: AI 回复没有渲染 Markdown?**

只有包含换行、代码块(` ``` `)、加粗(`**`)、列表(`- `)或标题(`#`)的内容才会用 Markdown 渲染,纯文本直接显示。