{"repo": "chiyuki0325/termhelper", "files": {"CLAUDE.md": {"1abb3cd9ad2c3f88603a963a1fd94e9438dd76b8": {"t": 1785747193.073699, "n": 13370, "h": "b692a7068225cbed", "c": "# CLAUDE.md — termhelper\n\n> Linux 终端智能助手。用仓颉语言编写,调用大模型将自然语言转换为可执行的 Shell 命令,辅以分解说明和安全警告。\n\n## 项目概述\n\n用户在终端输入 `?? <自然语言查询>`,termhelper 启动全屏 TUI,调用 LLM 生成命令,展示命令分解说明和安全等级,让用户选择运行/复制/修改。对于交互式命令(如 `sudo apt upgrade`),支持 PTY agentic 模式让 LLM 接管终端自动交互。\n\n**核心技术栈:** 仓颉(Cangjie)1.1.0 + ratatui TUI 框架 + Rust FFI 绑定 + `stdx.net.http` HTTP 客户端\n\n## 构建与运行\n\n### 首次配置\n\n```bash\n# 1. 拉取第三方依赖(stdx、naivejson、ratatui),构建 Rust FFI\nbash scripts/setup-deps.sh\n\n# 2. 编译项目\ncjpm build\n\n# 3. 运行(需要设置 API Key)\nexport LLM_API_KEY=\"sk-xxx\"\n./target/release/bin/termhelper \"删除所有 tmp 开头的文件夹\"\n```\n\n### 日常开发\n\n```bash\ncjpm build # 编译(输出到 target/release/bin/termhelper)\ncjpm build --verbose # 查看完整编译命令\n./target/release/bin/termhelper \"查询内容\" # 构建后运行\n./target/release/bin/termhelper --help # 查看帮助\n./target/release/bin/termhelper --debug \"查询\" # CLI 调试模式,打印 LLMResponse JSON\n```\n\n当前构建将 stdx、ratatui SDK、ratatui Rust FFI 和仓颉运行时静态链接进主程序;发行版自带的系统 C/C++ 运行库仍保持动态依赖。\n\n### 配置文件\n\n`~/.config/termhelper/config.json`(自动创建,权限 0600):\n\n```jsonc\n{\n \"version\": 1,\n \"provider\": \"openai_compat\", // openai_compat | anthropic | google\n \"llm\": {\n \"api_key\": \"\", // 也可用环境变量 LLM_API_KEY / OPENAI_API_KEY\n \"base_url\": \"https://api.openai.com/v1\",\n \"model\": \"gpt-4o\",\n \"timeout_sec\": 60,\n \"structured_output_mode\": \"auto\" // auto | json_object | json_schema\n },\n \"execution\": {\n \"spawn_timeout_sec\": 300,\n \"pty_default_mode\": \"confirm\", // confirm | full_auto\n \"pty_max_rounds\": 50\n },\n \"retry\": { \"max_retries\": 3, \"base_delay_ms\": 1000, \"max_delay_ms\": 16000 }\n}\n```\n\n### LLM Provider 支持\n\n| Provider | 配置值 | Structured Output 机制 |\n|----------|--------|----------------------|\n| OpenAI 兼容(含 DeepSeek 等) | `openai_compat` | `response_format: json_schema`,不支持时降级为 `json_object` + prompt |\n| Anthropic | `anthropic` | `tool_use` 单工具(`input_schema` = response schema) |\n| Google | `google` | `response_schema` + `response_mime_type: application/json` |\n\n## 架构设计\n\n### 四层架构\n\n```\n入口 (main.cj) → TUI 层 (tui/) → 核心层 (core/) → 适配器层 (adapters/)\n ↓ → 基础设施层 (infra/)\n Channel → 工具层 (util/)\n (后台 → TUI 单向)\n ↑\n types/ (零依赖,纯数据)\n```\n\n**包依赖规则:**\n- `types/` — 零依赖,被所有包引用。定义 LLM 类型、会话模型、PTY agentic 类型、AppEvent 等\n- `core/` — 依赖 adapters 接口(不依赖具体实现)、infra、util\n- `tui/` — 依赖 core、infra\n- `adapters/` — 不依赖 core/tui(仅依赖 types)\n- `infra/` — 不依赖 core/adapters/tui(仅依赖 types)\n- `util/` — 不依赖其他业务包\n\n### 线程模型\n\n**核心原则:Session(对话历史)仅主线程持有。** 后台任务通过 Channel 单向通知 TUI。\n\n```\nMain Thread (TUI) Background Tasks\n┌──────────────────────┐ ┌──────────────────────┐\n│ ratatui 事件循环 │ Channel │ LLM Task │\n│ pollEvent + draw │◄─────────│ HTTP 请求 + SSE 解析 │\n│ Channel.tryRecv() │ │ Structured Output │\n│ session.append() │ │ 重试逻辑 │\n│ 键盘 → Action 分发 │ └──────────────────────┘\n└──────────────────────┘ ┌──────────────────────┐\n │ Executor Task │\n │ spawn 子进程 │\n │ PTY I/O 读写 │\n │ PTY agentic 循环 │\n └──────────────────────┘\n```\n\n### 屏幕状态机\n\n```\nSTART → Loading ──┬──→ Result ──┬──→ Execute ──→ Result\n │ ├──→ PTY Agentic\n ├──→ Prompt │ (交互式命令)\n │ (Investigate/Clarify/Modify)\n └──→ Error └──→ 退出 Shell\n```\n\n- **Loading** — spinner + 流式 LLM 文本实时展示\n- **Result** — 命令 + 分解说明 + 安全警告 + 选项菜单(运行/复制/修改/退出)\n- **Execute** — 普通 spawn 执行,实时滚动输出\n- **Prompt.Investigate** — 环境调查:LLM 提出诊断命令,用户逐条/批量授权\n- **Prompt.Clarify** — LLM 询问澄清\n- **Prompt.Modify** — 用户修改命令\n- **Error** — 错误展示 + 重试/退出\n\n### LLM 调用流程\n\n```\nSession.start(query)\n → RequestBuilder.build() // system prompt + context + history + JSON Schema\n → LLMProvider.chat(chatRequest) // 多态分派到具体 Provider\n → HTTP POST + Structured Output 配置\n → 响应校验(json_schema / json_object)\n → parseLLMResponseJson() → LLMResponse(Command/Investigate/Clarify)\n → Session.handleResponse() // SafetyFallback 兜底 + FactEdits 合并\n → SessionAction(ShowCommand/RequestInvestigate/AskClarify/Error)\n```\n\n### PTY Agentic 循环(交互式命令)\n\n交互式命令(`interactive: true`)走 PTY agentic 循环,核心流程:\n1. `infra/pty.cj` 创建 PTY(`/dev/ptmx` + fork + exec),设置 `TIOCSWINSZ`\n2. 非阻塞读 master fd → LLM tool-calling 分析输出(write/ask_user/wait/exit/interrupt)\n3. 写 master fd → 继续读,子进程退出则结束\n4. 节流控制:最小调用间隔 2000ms、最小新增字节 256、最大 50 轮\n\n## 源码目录结构\n\n```\nsrc/\n├── main.cj # 入口:参数解析、配置加载、TUI/调试/安装卸载分发\n├── types/ # 零依赖数据模型\n│ ├── llm.cj # LLMResponse, CommandData, InvestigateData, SafetyLevel\n│ ├── session.cj # Message, MessageRole, EnvironmentContext\n│ └── pty.cj # PtyTool, PtyLoopState, PtyResult, AppEvent, CommandResult\n├── core/ # 业务编排层\n│ ├── session.cj # Session:对话历史、LLM 交互、FactEdits 合并\n│ ├── request.cj # RequestBuilder:system prompt + context + history + JSON Schema\n│ ├── context.cj # ContextManager:环境上下文持久化和迁移入口\n│ ├── pty_agent.cj # PTY agentic 循环:PTY + LLM tool-calling + 用户确认\n│ ├── safety_fallback.cj # 本地安全兜底检查\n│ └── retry.cj # LLM 重试策略和错误分类\n├── adapters/ # LLM Provider 适配层\n│ ├── provider.cj # Provider 抽象、ChatRequest/ChatResponse、tool-calling 类型\n│ ├── openai_compat.cj # OpenAI 兼容接口\n│ ├── anthropic.cj # Anthropic Messages API\n│ ├── google.cj # Google Gemini API\n│ ├── structured_output.cj # Provider-specific structured output 参数构造\n│ ├── sse.cj # SSE 流解析器\n│ └── util.cj # Provider/SSE 共用 JSON 工具函数\n├── infra/ # OS 和持久化基础设施\n│ ├── config.cj # 配置加载、环境变量覆盖、默认配置落盘\n│ ├── paths.cj # 配置和上下文路径\n│ ├── fs.cj # 文件读写、flock 排他锁、原子写入\n│ ├── file_permissions.cj # 敏感文件 chmod 0600\n│ ├── migration.cj # context.json 版本迁移\n│ ├── spawn.cj # 普通子进程执行、输出捕获、超时和中断\n│ ├── pty.cj # PTY 创建、窗口大小、master fd 读写、子进程控制\n│ ├── pty_text_buffer.cj # PTY 输出纯文本归一化、LLM 上下文 compact/delta\n│ ├── terminal_buffer.cj # 虚拟终端缓冲区和 ANSI 解析\n│ ├── clipboard.cj # 剪贴板:OSC 52 → wl-copy → xclip → 降级展示\n│ ├── shell_rc.cj # Shell RC install/uninstall,marker 包围\n│ └── util.cj # debug trace、环境变量、进程/信号工具\n├── tui/ # 终端 UI 层\n│ ├── tui.cj # TUI 初始化、主循环、Screen 路由、Channel 接收\n│ ├── input.cj # 键盘/鼠标事件 → Action\n│ ├── signal.cj # self-pipe 信号通知辅助\n│ ├── theme.cj # 颜色和样式常量\n│ ├── screens/\n│ │ ├── loading.cj # Loading 屏\n│ │ ├── result.cj # 命令结果屏\n│ │ ├── execute.cj # 普通 spawn 执行屏\n│ │ ├── pty_agentic.cj # PTY agentic 执行屏\n│ │ ├── prompt.cj # investigate/clarify/modify 输入屏\n│ │ └── error.cj # 错误屏\n│ └── components/\n│ ├── layout.cj # 通用布局辅助\n│ ├── command_display.cj # 命令展示\n│ ├── explanation.cj # 命令分解说明\n│ ├── safety_badge.cj # 安全等级展示\n│ ├── option_menu.cj # 选项菜单\n│ ├── overlay_panel.cj # PTY overlay 面板\n│ ├── spinner.cj # 加载动画\n│ ├── styled_text.cj # 富文本样式辅助\n│ └── text_input.cj # 文本输入/敏感输入\n├── util/ # Prompt 和 schema 工具\n│ ├── prompt.cj # 主流程、json_object、PTY agent prompt\n│ └── json_schema.cj # LLMResponse JSON Schema\n├── i18n/\n│ └── i18n.cj # LANG/LC_ALL 语言检测和中英文 UI/prompt 文案\n```\n\n## 编码约定与注意事项\n\n### 仓颉语言关键规则\n\n- **`std.core` 自动导入**,无需显式 import。`String`、`Array`、`Option`、`Rune` 等均可直接用\n- **`safeUtf8(Array)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array)`\n- **`@JsonAdapter`** 宏来自 `naivejson`,为类/枚举自动生成 `serialize()`/`deserialize()`/`toJsonSchema()`\n- **`@C` 结构体** 用于 FFI 场景,如 `Winsize` 用于 `ioctl(TIOCSWINSZ)`\n- **`StringBuilder`** 用于高效拼接字符串,避免大量 `+` 产生中间字符串\n- **`unsafe {}` 块** 包裹所有 FFI 函数调用(`read`/`write`/`fork`/`ioctl`/`close`/`kill` 等)\n- **`Attribute` 用 `var` 不用 `let`**:`class` 的字段必须用 `var` 声明(即使只在构造后赋值)\n- **`spawn {}`** 创建新线程,返回 `Future`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 safeUtf8\n\n项目中禁止用 `String(Rune(Int32(byte)))` 逐字节拼接字符串;这种写法会破坏多字节 UTF-8 序列,导致中文和其他非 ASCII 输出乱码。\n\n当前 PTY LLM 文本上下文使用 `src/infra/pty_text_buffer.cj` 中的 `safeUtf8(bytes)` 做容错转换。它会保留合法 UTF-8 序列,并用替换符处理截断或非法字节,适合处理 PTY tail、delta、ANSI 剥离后的片段等不保证边界完整的终端输出:\n\n```cangjie\n// ✅ PTY/LLM 文本上下文:容错处理不完整或非法 UTF-8\nsafeUtf8(bytesRange(bytes, start, end).toArray())\n\n// ❌ 错误做法:逐字节转 Rune 会破坏多字节 UTF-8\nString(Rune(Int32(byte)))\n```\n\n已确认完整的 UTF-8 字节数组仍使用 `String.fromUtf8(Array)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option` 为 `None` 时不出现在 JSON 输出中\n- **`@JsonName[\"alt_name\"]`** 自定义字段名映射\n- LLMResponse 是 **payloaded enum(无 @JsonAdapter)**,需手动 dispatch 解析\n\n### 配置加载优先级\n\n1. Provider 专属环境变量(`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`)\n2. 通用环境变量(`LLM_API_KEY`,其次 `OPENAI_API_KEY`)\n3. `~/.config/termhelper/config.json` → `llm.api_key`\n4. 无配置时 TUI 提示\n\n### 文件锁\n\n`context.json` 读写全程使用 `flock(LOCK_EX)` 排他锁,防止多实例丢失更新。\n\n### 国际化\n\n`src/i18n/i18n.cj` 根据 `LC_ALL`/`LANG` 环境变量自动切换中英文。UI 文本通过 `uiText(key)` 获取,prompt 中的 few-shot examples 也随语言切换。\n\n### 调试跟踪\n\n`src/infra/util.cj` 提供 `debugTrace(msg)` 和 `enableDebugTrace()`。CLI 调试模式(`--debug`)会将完整 LLMResponse JSON 打印到 stdout。\n\n## 第三方依赖\n\n| 依赖 | 路径 | 说明 |\n|------|------|------|\n| stdx | `thirdparty/stdx/` | 仓颉扩展标准库 release 包,`setup-deps.sh` 按当前架构下载 |\n| ratatui | `thirdparty/ratatui/` | Rust FFI 绑定的 TUI 框架(cangjie-ratatui-sdk + cangjie-tui-ffi) |\n| naivejson | `thirdparty/naivejson/` | JSON 序列化库,提供 `@JsonAdapter` 宏和 `DataModel` 转换 |\n\n`references/` 目录包含参考实现(claude-code SDK 源码、musl libc PTY 实现、ratatui/naivejson 的本地备份)。\n\n### thirdparty 管理规则\n\n`thirdparty/` 整体被主仓库 `.gitignore` 忽略,不提交第三方源码、release 包、解压产物或 Rust target 产物。第三方依赖的获取和本地修补统一由 `scripts/setup-deps.sh` 完成:\n\n- stdx:按当前 CPU 架构下载 `cangjie_stdx` release zip,解压到 `thirdparty/stdx/_cjnative/`。\n- naivejson:clone 后 patch 其 `naivejson/cjpm.toml` 中当前架构的 stdx 路径,指向本项目 `thirdparty/stdx/.../static/stdx`。\n- ratatui:clone 后 patch SDK 的 Rust FFI link path,并构建 `cangjie-tui-ffi`。\n- 主项目:`setup-deps.sh` 会 patch 根目录 `cjpm.toml` 的 ratatui FFI `link-option`,把提交版相对路径替换为当前 checkout 的绝对路径,供本机 `cjpm build` 使用。\n\n绝对路径不应该进入提交。尤其是 `setup-deps.sh` 运行后,根目录 `cjpm.toml` 可能出现当前机器的绝对路径(例如 `-L.../thirdparty/ratatui/cangjie-tui-ffi/target/release`)。如果本轮需要修改并提交 `cjpm.toml`,提交前必须把这些本地 patch 恢复为仓库约定的相对路径;如果本轮没有修改 `cjpm.toml`,不要 add 它。提交前用以下命令检查:\n\n```bash\nrg -n \"/home/|/Users/|Documents/Apps|cangjie-tui-ffi/target/release\" cjpm.toml scripts/setup-deps.sh README.md CLAUDE.md\ngit diff --cached -- cjpm.toml\n```\n\n### ratatui SDK 注意事项\n\n`thirdparty/ratatui/` 中的 Cangjie ratatui SDK 是 `gitcode.com/Cangjie-SIG/ratatui` 的 hard fork。原始 SDK 质量低劣,包含大量 stub 代码,容易导致功能静默失败。\n\n开发时应将该 SDK 视为不可靠依赖:遇到 TUI 行为异常、FFI 返回成功但无实际渲染、事件/样式/布局接口无效等情况,不要默认业务代码有错,应主动检查并修改 `thirdparty/ratatui/`,补完 termhelper 所需功能。\n\n## Bundled Skills 使用说明\n\n本项目在 `.claude/skills/` 中绑定了仓颉语言相关 skills,Codex/Claude Code 等编码代理开发本项目时应优先使用这些资料,而不是凭经验猜测仓颉 API:\n\n- `cangjie-lang-features` — 仓颉语言核心特性\n- `cangjie-std` — 标准库常用功能速查\n- `cangjie-stdx` — 扩展标准库功能速查(JSON、HTTP、日志、压缩、TLS 等)\n- `cangjie-toolchains` — `cjc` / `cjpm` / `cjfmt` / `cjlint` 等工具链\n- `cangjie-regulations` — 仓颉项目规范和最佳实践\n- `cangjie-original-docs` — 原始文档兜底\n\n实现新功能时,优先级应为:\n\n1. 仓颉标准库 `std`。\n2. 仓颉扩展标准库 `stdx`。\n3. 项目已有 helper / infra 封装。\n4. 第三方依赖(如 `naivejson`、`ratatui`)。\n5. FFI。\n\n不要第一反应写 FFI。只有在 `std` / `stdx` / 现有封装无法满足需求,或确实需要 POSIX/终端/PTY/底层系统能力时,才新增 FFI。新增 FFI 时必须集中声明、使用 `unsafe {}` 包裹调用、处理资源释放,并优先参考本项目已有 `infra/pty.cj`、`infra/spawn.cj`、`infra/file_permissions.cj` 的写法。\n\n## Git Commit Format\n\nUse English commit messages in the format `type: description`:\n- `feat:` — new features\n- `fix:` — bug fixes\n- `refactor:` — refactoring\n- `improvement:` — improvements\n- `docs:` — documentation\n- `chore:` — maintenance\n\n**注意**: 对于 Claude Code,每个 commit message 末尾必须包含 `Co-Authored-By` trailer。模型名称需要参考运行环境提示中的 `You are powered by the model <实际的模型名称>`,不应该无脑认为自己是 `Claude Opus 4.8`。\n\n对于 Codex,每个 commit message 末尾也必须包含 `Co-Authored-By` trailer,使用当前 Codex 模型身份。例如本项目当前约定为:\n\n```text\nCo-Authored-By: GPT-5.5 \n```\n\n## Memory 文件\n\n项目相关的持久记忆存放在 `/home/chiyuki/.claude/projects/-home-chiyuki-Projects-termhelper/memory/`,启动时通过 `MEMORY.md` 索引加载。\n"}, "77faf4ceb59c1b901bce6b2d69acbab553d91cef": {"t": 1785771231.351088, "n": 13370, "h": "b692a7068225cbed", "c": "# CLAUDE.md — termhelper\n\n> Linux 终端智能助手。用仓颉语言编写,调用大模型将自然语言转换为可执行的 Shell 命令,辅以分解说明和安全警告。\n\n## 项目概述\n\n用户在终端输入 `?? <自然语言查询>`,termhelper 启动全屏 TUI,调用 LLM 生成命令,展示命令分解说明和安全等级,让用户选择运行/复制/修改。对于交互式命令(如 `sudo apt upgrade`),支持 PTY agentic 模式让 LLM 接管终端自动交互。\n\n**核心技术栈:** 仓颉(Cangjie)1.1.0 + ratatui TUI 框架 + Rust FFI 绑定 + `stdx.net.http` HTTP 客户端\n\n## 构建与运行\n\n### 首次配置\n\n```bash\n# 1. 拉取第三方依赖(stdx、naivejson、ratatui),构建 Rust FFI\nbash scripts/setup-deps.sh\n\n# 2. 编译项目\ncjpm build\n\n# 3. 运行(需要设置 API Key)\nexport LLM_API_KEY=\"sk-xxx\"\n./target/release/bin/termhelper \"删除所有 tmp 开头的文件夹\"\n```\n\n### 日常开发\n\n```bash\ncjpm build # 编译(输出到 target/release/bin/termhelper)\ncjpm build --verbose # 查看完整编译命令\n./target/release/bin/termhelper \"查询内容\" # 构建后运行\n./target/release/bin/termhelper --help # 查看帮助\n./target/release/bin/termhelper --debug \"查询\" # CLI 调试模式,打印 LLMResponse JSON\n```\n\n当前构建将 stdx、ratatui SDK、ratatui Rust FFI 和仓颉运行时静态链接进主程序;发行版自带的系统 C/C++ 运行库仍保持动态依赖。\n\n### 配置文件\n\n`~/.config/termhelper/config.json`(自动创建,权限 0600):\n\n```jsonc\n{\n \"version\": 1,\n \"provider\": \"openai_compat\", // openai_compat | anthropic | google\n \"llm\": {\n \"api_key\": \"\", // 也可用环境变量 LLM_API_KEY / OPENAI_API_KEY\n \"base_url\": \"https://api.openai.com/v1\",\n \"model\": \"gpt-4o\",\n \"timeout_sec\": 60,\n \"structured_output_mode\": \"auto\" // auto | json_object | json_schema\n },\n \"execution\": {\n \"spawn_timeout_sec\": 300,\n \"pty_default_mode\": \"confirm\", // confirm | full_auto\n \"pty_max_rounds\": 50\n },\n \"retry\": { \"max_retries\": 3, \"base_delay_ms\": 1000, \"max_delay_ms\": 16000 }\n}\n```\n\n### LLM Provider 支持\n\n| Provider | 配置值 | Structured Output 机制 |\n|----------|--------|----------------------|\n| OpenAI 兼容(含 DeepSeek 等) | `openai_compat` | `response_format: json_schema`,不支持时降级为 `json_object` + prompt |\n| Anthropic | `anthropic` | `tool_use` 单工具(`input_schema` = response schema) |\n| Google | `google` | `response_schema` + `response_mime_type: application/json` |\n\n## 架构设计\n\n### 四层架构\n\n```\n入口 (main.cj) → TUI 层 (tui/) → 核心层 (core/) → 适配器层 (adapters/)\n ↓ → 基础设施层 (infra/)\n Channel → 工具层 (util/)\n (后台 → TUI 单向)\n ↑\n types/ (零依赖,纯数据)\n```\n\n**包依赖规则:**\n- `types/` — 零依赖,被所有包引用。定义 LLM 类型、会话模型、PTY agentic 类型、AppEvent 等\n- `core/` — 依赖 adapters 接口(不依赖具体实现)、infra、util\n- `tui/` — 依赖 core、infra\n- `adapters/` — 不依赖 core/tui(仅依赖 types)\n- `infra/` — 不依赖 core/adapters/tui(仅依赖 types)\n- `util/` — 不依赖其他业务包\n\n### 线程模型\n\n**核心原则:Session(对话历史)仅主线程持有。** 后台任务通过 Channel 单向通知 TUI。\n\n```\nMain Thread (TUI) Background Tasks\n┌──────────────────────┐ ┌──────────────────────┐\n│ ratatui 事件循环 │ Channel │ LLM Task │\n│ pollEvent + draw │◄─────────│ HTTP 请求 + SSE 解析 │\n│ Channel.tryRecv() │ │ Structured Output │\n│ session.append() │ │ 重试逻辑 │\n│ 键盘 → Action 分发 │ └──────────────────────┘\n└──────────────────────┘ ┌──────────────────────┐\n │ Executor Task │\n │ spawn 子进程 │\n │ PTY I/O 读写 │\n │ PTY agentic 循环 │\n └──────────────────────┘\n```\n\n### 屏幕状态机\n\n```\nSTART → Loading ──┬──→ Result ──┬──→ Execute ──→ Result\n │ ├──→ PTY Agentic\n ├──→ Prompt │ (交互式命令)\n │ (Investigate/Clarify/Modify)\n └──→ Error └──→ 退出 Shell\n```\n\n- **Loading** — spinner + 流式 LLM 文本实时展示\n- **Result** — 命令 + 分解说明 + 安全警告 + 选项菜单(运行/复制/修改/退出)\n- **Execute** — 普通 spawn 执行,实时滚动输出\n- **Prompt.Investigate** — 环境调查:LLM 提出诊断命令,用户逐条/批量授权\n- **Prompt.Clarify** — LLM 询问澄清\n- **Prompt.Modify** — 用户修改命令\n- **Error** — 错误展示 + 重试/退出\n\n### LLM 调用流程\n\n```\nSession.start(query)\n → RequestBuilder.build() // system prompt + context + history + JSON Schema\n → LLMProvider.chat(chatRequest) // 多态分派到具体 Provider\n → HTTP POST + Structured Output 配置\n → 响应校验(json_schema / json_object)\n → parseLLMResponseJson() → LLMResponse(Command/Investigate/Clarify)\n → Session.handleResponse() // SafetyFallback 兜底 + FactEdits 合并\n → SessionAction(ShowCommand/RequestInvestigate/AskClarify/Error)\n```\n\n### PTY Agentic 循环(交互式命令)\n\n交互式命令(`interactive: true`)走 PTY agentic 循环,核心流程:\n1. `infra/pty.cj` 创建 PTY(`/dev/ptmx` + fork + exec),设置 `TIOCSWINSZ`\n2. 非阻塞读 master fd → LLM tool-calling 分析输出(write/ask_user/wait/exit/interrupt)\n3. 写 master fd → 继续读,子进程退出则结束\n4. 节流控制:最小调用间隔 2000ms、最小新增字节 256、最大 50 轮\n\n## 源码目录结构\n\n```\nsrc/\n├── main.cj # 入口:参数解析、配置加载、TUI/调试/安装卸载分发\n├── types/ # 零依赖数据模型\n│ ├── llm.cj # LLMResponse, CommandData, InvestigateData, SafetyLevel\n│ ├── session.cj # Message, MessageRole, EnvironmentContext\n│ └── pty.cj # PtyTool, PtyLoopState, PtyResult, AppEvent, CommandResult\n├── core/ # 业务编排层\n│ ├── session.cj # Session:对话历史、LLM 交互、FactEdits 合并\n│ ├── request.cj # RequestBuilder:system prompt + context + history + JSON Schema\n│ ├── context.cj # ContextManager:环境上下文持久化和迁移入口\n│ ├── pty_agent.cj # PTY agentic 循环:PTY + LLM tool-calling + 用户确认\n│ ├── safety_fallback.cj # 本地安全兜底检查\n│ └── retry.cj # LLM 重试策略和错误分类\n├── adapters/ # LLM Provider 适配层\n│ ├── provider.cj # Provider 抽象、ChatRequest/ChatResponse、tool-calling 类型\n│ ├── openai_compat.cj # OpenAI 兼容接口\n│ ├── anthropic.cj # Anthropic Messages API\n│ ├── google.cj # Google Gemini API\n│ ├── structured_output.cj # Provider-specific structured output 参数构造\n│ ├── sse.cj # SSE 流解析器\n│ └── util.cj # Provider/SSE 共用 JSON 工具函数\n├── infra/ # OS 和持久化基础设施\n│ ├── config.cj # 配置加载、环境变量覆盖、默认配置落盘\n│ ├── paths.cj # 配置和上下文路径\n│ ├── fs.cj # 文件读写、flock 排他锁、原子写入\n│ ├── file_permissions.cj # 敏感文件 chmod 0600\n│ ├── migration.cj # context.json 版本迁移\n│ ├── spawn.cj # 普通子进程执行、输出捕获、超时和中断\n│ ├── pty.cj # PTY 创建、窗口大小、master fd 读写、子进程控制\n│ ├── pty_text_buffer.cj # PTY 输出纯文本归一化、LLM 上下文 compact/delta\n│ ├── terminal_buffer.cj # 虚拟终端缓冲区和 ANSI 解析\n│ ├── clipboard.cj # 剪贴板:OSC 52 → wl-copy → xclip → 降级展示\n│ ├── shell_rc.cj # Shell RC install/uninstall,marker 包围\n│ └── util.cj # debug trace、环境变量、进程/信号工具\n├── tui/ # 终端 UI 层\n│ ├── tui.cj # TUI 初始化、主循环、Screen 路由、Channel 接收\n│ ├── input.cj # 键盘/鼠标事件 → Action\n│ ├── signal.cj # self-pipe 信号通知辅助\n│ ├── theme.cj # 颜色和样式常量\n│ ├── screens/\n│ │ ├── loading.cj # Loading 屏\n│ │ ├── result.cj # 命令结果屏\n│ │ ├── execute.cj # 普通 spawn 执行屏\n│ │ ├── pty_agentic.cj # PTY agentic 执行屏\n│ │ ├── prompt.cj # investigate/clarify/modify 输入屏\n│ │ └── error.cj # 错误屏\n│ └── components/\n│ ├── layout.cj # 通用布局辅助\n│ ├── command_display.cj # 命令展示\n│ ├── explanation.cj # 命令分解说明\n│ ├── safety_badge.cj # 安全等级展示\n│ ├── option_menu.cj # 选项菜单\n│ ├── overlay_panel.cj # PTY overlay 面板\n│ ├── spinner.cj # 加载动画\n│ ├── styled_text.cj # 富文本样式辅助\n│ └── text_input.cj # 文本输入/敏感输入\n├── util/ # Prompt 和 schema 工具\n│ ├── prompt.cj # 主流程、json_object、PTY agent prompt\n│ └── json_schema.cj # LLMResponse JSON Schema\n├── i18n/\n│ └── i18n.cj # LANG/LC_ALL 语言检测和中英文 UI/prompt 文案\n```\n\n## 编码约定与注意事项\n\n### 仓颉语言关键规则\n\n- **`std.core` 自动导入**,无需显式 import。`String`、`Array`、`Option`、`Rune` 等均可直接用\n- **`safeUtf8(Array)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array)`\n- **`@JsonAdapter`** 宏来自 `naivejson`,为类/枚举自动生成 `serialize()`/`deserialize()`/`toJsonSchema()`\n- **`@C` 结构体** 用于 FFI 场景,如 `Winsize` 用于 `ioctl(TIOCSWINSZ)`\n- **`StringBuilder`** 用于高效拼接字符串,避免大量 `+` 产生中间字符串\n- **`unsafe {}` 块** 包裹所有 FFI 函数调用(`read`/`write`/`fork`/`ioctl`/`close`/`kill` 等)\n- **`Attribute` 用 `var` 不用 `let`**:`class` 的字段必须用 `var` 声明(即使只在构造后赋值)\n- **`spawn {}`** 创建新线程,返回 `Future`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 safeUtf8\n\n项目中禁止用 `String(Rune(Int32(byte)))` 逐字节拼接字符串;这种写法会破坏多字节 UTF-8 序列,导致中文和其他非 ASCII 输出乱码。\n\n当前 PTY LLM 文本上下文使用 `src/infra/pty_text_buffer.cj` 中的 `safeUtf8(bytes)` 做容错转换。它会保留合法 UTF-8 序列,并用替换符处理截断或非法字节,适合处理 PTY tail、delta、ANSI 剥离后的片段等不保证边界完整的终端输出:\n\n```cangjie\n// ✅ PTY/LLM 文本上下文:容错处理不完整或非法 UTF-8\nsafeUtf8(bytesRange(bytes, start, end).toArray())\n\n// ❌ 错误做法:逐字节转 Rune 会破坏多字节 UTF-8\nString(Rune(Int32(byte)))\n```\n\n已确认完整的 UTF-8 字节数组仍使用 `String.fromUtf8(Array)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option` 为 `None` 时不出现在 JSON 输出中\n- **`@JsonName[\"alt_name\"]`** 自定义字段名映射\n- LLMResponse 是 **payloaded enum(无 @JsonAdapter)**,需手动 dispatch 解析\n\n### 配置加载优先级\n\n1. Provider 专属环境变量(`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`)\n2. 通用环境变量(`LLM_API_KEY`,其次 `OPENAI_API_KEY`)\n3. `~/.config/termhelper/config.json` → `llm.api_key`\n4. 无配置时 TUI 提示\n\n### 文件锁\n\n`context.json` 读写全程使用 `flock(LOCK_EX)` 排他锁,防止多实例丢失更新。\n\n### 国际化\n\n`src/i18n/i18n.cj` 根据 `LC_ALL`/`LANG` 环境变量自动切换中英文。UI 文本通过 `uiText(key)` 获取,prompt 中的 few-shot examples 也随语言切换。\n\n### 调试跟踪\n\n`src/infra/util.cj` 提供 `debugTrace(msg)` 和 `enableDebugTrace()`。CLI 调试模式(`--debug`)会将完整 LLMResponse JSON 打印到 stdout。\n\n## 第三方依赖\n\n| 依赖 | 路径 | 说明 |\n|------|------|------|\n| stdx | `thirdparty/stdx/` | 仓颉扩展标准库 release 包,`setup-deps.sh` 按当前架构下载 |\n| ratatui | `thirdparty/ratatui/` | Rust FFI 绑定的 TUI 框架(cangjie-ratatui-sdk + cangjie-tui-ffi) |\n| naivejson | `thirdparty/naivejson/` | JSON 序列化库,提供 `@JsonAdapter` 宏和 `DataModel` 转换 |\n\n`references/` 目录包含参考实现(claude-code SDK 源码、musl libc PTY 实现、ratatui/naivejson 的本地备份)。\n\n### thirdparty 管理规则\n\n`thirdparty/` 整体被主仓库 `.gitignore` 忽略,不提交第三方源码、release 包、解压产物或 Rust target 产物。第三方依赖的获取和本地修补统一由 `scripts/setup-deps.sh` 完成:\n\n- stdx:按当前 CPU 架构下载 `cangjie_stdx` release zip,解压到 `thirdparty/stdx/_cjnative/`。\n- naivejson:clone 后 patch 其 `naivejson/cjpm.toml` 中当前架构的 stdx 路径,指向本项目 `thirdparty/stdx/.../static/stdx`。\n- ratatui:clone 后 patch SDK 的 Rust FFI link path,并构建 `cangjie-tui-ffi`。\n- 主项目:`setup-deps.sh` 会 patch 根目录 `cjpm.toml` 的 ratatui FFI `link-option`,把提交版相对路径替换为当前 checkout 的绝对路径,供本机 `cjpm build` 使用。\n\n绝对路径不应该进入提交。尤其是 `setup-deps.sh` 运行后,根目录 `cjpm.toml` 可能出现当前机器的绝对路径(例如 `-L.../thirdparty/ratatui/cangjie-tui-ffi/target/release`)。如果本轮需要修改并提交 `cjpm.toml`,提交前必须把这些本地 patch 恢复为仓库约定的相对路径;如果本轮没有修改 `cjpm.toml`,不要 add 它。提交前用以下命令检查:\n\n```bash\nrg -n \"/home/|/Users/|Documents/Apps|cangjie-tui-ffi/target/release\" cjpm.toml scripts/setup-deps.sh README.md CLAUDE.md\ngit diff --cached -- cjpm.toml\n```\n\n### ratatui SDK 注意事项\n\n`thirdparty/ratatui/` 中的 Cangjie ratatui SDK 是 `gitcode.com/Cangjie-SIG/ratatui` 的 hard fork。原始 SDK 质量低劣,包含大量 stub 代码,容易导致功能静默失败。\n\n开发时应将该 SDK 视为不可靠依赖:遇到 TUI 行为异常、FFI 返回成功但无实际渲染、事件/样式/布局接口无效等情况,不要默认业务代码有错,应主动检查并修改 `thirdparty/ratatui/`,补完 termhelper 所需功能。\n\n## Bundled Skills 使用说明\n\n本项目在 `.claude/skills/` 中绑定了仓颉语言相关 skills,Codex/Claude Code 等编码代理开发本项目时应优先使用这些资料,而不是凭经验猜测仓颉 API:\n\n- `cangjie-lang-features` — 仓颉语言核心特性\n- `cangjie-std` — 标准库常用功能速查\n- `cangjie-stdx` — 扩展标准库功能速查(JSON、HTTP、日志、压缩、TLS 等)\n- `cangjie-toolchains` — `cjc` / `cjpm` / `cjfmt` / `cjlint` 等工具链\n- `cangjie-regulations` — 仓颉项目规范和最佳实践\n- `cangjie-original-docs` — 原始文档兜底\n\n实现新功能时,优先级应为:\n\n1. 仓颉标准库 `std`。\n2. 仓颉扩展标准库 `stdx`。\n3. 项目已有 helper / infra 封装。\n4. 第三方依赖(如 `naivejson`、`ratatui`)。\n5. FFI。\n\n不要第一反应写 FFI。只有在 `std` / `stdx` / 现有封装无法满足需求,或确实需要 POSIX/终端/PTY/底层系统能力时,才新增 FFI。新增 FFI 时必须集中声明、使用 `unsafe {}` 包裹调用、处理资源释放,并优先参考本项目已有 `infra/pty.cj`、`infra/spawn.cj`、`infra/file_permissions.cj` 的写法。\n\n## Git Commit Format\n\nUse English commit messages in the format `type: description`:\n- `feat:` — new features\n- `fix:` — bug fixes\n- `refactor:` — refactoring\n- `improvement:` — improvements\n- `docs:` — documentation\n- `chore:` — maintenance\n\n**注意**: 对于 Claude Code,每个 commit message 末尾必须包含 `Co-Authored-By` trailer。模型名称需要参考运行环境提示中的 `You are powered by the model <实际的模型名称>`,不应该无脑认为自己是 `Claude Opus 4.8`。\n\n对于 Codex,每个 commit message 末尾也必须包含 `Co-Authored-By` trailer,使用当前 Codex 模型身份。例如本项目当前约定为:\n\n```text\nCo-Authored-By: GPT-5.5 \n```\n\n## Memory 文件\n\n项目相关的持久记忆存放在 `/home/chiyuki/.claude/projects/-home-chiyuki-Projects-termhelper/memory/`,启动时通过 `MEMORY.md` 索引加载。\n"}, "2de378ca3388a94a39f5b9aa993286c9f38406d7": {"t": 1785771234.5803404, "n": 13623, "h": "5a3e0830c336ee06", "c": "# CLAUDE.md — termhelper\n\n> Linux 终端智能助手。用仓颉语言编写,调用大模型将自然语言转换为可执行的 Shell 命令,辅以分解说明和安全警告。\n\n## 项目概述\n\n用户在终端输入 `?? <自然语言查询>`,termhelper 启动全屏 TUI,调用 LLM 生成命令,展示命令分解说明和安全等级,让用户选择运行/复制/修改。对于交互式命令(如 `sudo apt upgrade`),支持 PTY agentic 模式让 LLM 接管终端自动交互。\n\n**核心技术栈:** 仓颉(Cangjie)1.1.0 + ratatui TUI 框架 + Rust FFI 绑定 + `stdx.net.http` HTTP 客户端\n\n## 构建与运行\n\n### 首次配置\n\n```bash\n# 1. 拉取第三方依赖(stdx、naivejson、ratatui),构建 Rust FFI\nbash scripts/setup-deps.sh\n\n# 2. 编译项目\ncjpm build\n\n# 3. 运行(需要设置 API Key)\nexport LLM_API_KEY=\"sk-xxx\"\n./target/release/bin/termhelper \"删除所有 tmp 开头的文件夹\"\n```\n\n### 日常开发\n\n```bash\ncjpm build # 编译(输出到 target/release/bin/termhelper)\ncjpm build --verbose # 查看完整编译命令\n./target/release/bin/termhelper \"查询内容\" # 构建后运行\n./target/release/bin/termhelper --help # 查看帮助\n./target/release/bin/termhelper --debug \"查询\" # CLI 调试模式,打印 LLMResponse JSON\n```\n\n当前构建将 stdx、ratatui SDK、ratatui Rust FFI 和仓颉运行时静态链接进主程序;发行版自带的系统 C/C++ 运行库仍保持动态依赖。\n\n### 配置文件\n\n`~/.config/termhelper/config.json`(自动创建,权限 0600):\n\n```jsonc\n{\n \"version\": 1,\n \"provider\": \"openai_compat\", // openai_compat | anthropic | google\n \"llm\": {\n \"api_key\": \"\", // 也可用环境变量 LLM_API_KEY / OPENAI_API_KEY\n \"base_url\": \"https://api.openai.com/v1\",\n \"model\": \"gpt-4o\",\n \"timeout_sec\": 60,\n \"structured_output_mode\": \"auto\" // auto | json_object | json_schema\n },\n \"execution\": {\n \"spawn_timeout_sec\": 300,\n \"pty_default_mode\": \"confirm\", // confirm | full_auto\n \"pty_max_rounds\": 50\n },\n \"retry\": { \"max_retries\": 3, \"base_delay_ms\": 1000, \"max_delay_ms\": 16000 }\n}\n```\n\n### LLM Provider 支持\n\n| Provider | 配置值 | Structured Output 机制 |\n|----------|--------|----------------------|\n| OpenAI 兼容(含 DeepSeek 等) | `openai_compat` | `response_format: json_schema`,不支持时降级为 `json_object` + prompt |\n| Anthropic | `anthropic` | `tool_use` 单工具(`input_schema` = response schema) |\n| Google | `google` | `response_schema` + `response_mime_type: application/json` |\n\n## 架构设计\n\n### 四层架构\n\n```\n入口 (main.cj) → TUI 层 (tui/) → 核心层 (core/) → 适配器层 (adapters/)\n ↓ → 基础设施层 (infra/)\n Channel → 工具层 (util/)\n (后台 → TUI 单向)\n ↑\n types/ (零依赖,纯数据)\n```\n\n**包依赖规则:**\n- `types/` — 零依赖,被所有包引用。定义 LLM 类型、会话模型、PTY agentic 类型、AppEvent 等\n- `core/` — 依赖 adapters 接口(不依赖具体实现)、infra、util\n- `tui/` — 依赖 core、infra\n- `adapters/` — 不依赖 core/tui(仅依赖 types)\n- `infra/` — 不依赖 core/adapters/tui(仅依赖 types)\n- `util/` — 不依赖其他业务包\n\n### 线程模型\n\n**核心原则:Session(对话历史)仅主线程持有。** 后台任务通过 Channel 单向通知 TUI。\n\n```\nMain Thread (TUI) Background Tasks\n┌──────────────────────┐ ┌──────────────────────┐\n│ ratatui 事件循环 │ Channel │ LLM Task │\n│ pollEvent + draw │◄─────────│ HTTP 请求 + SSE 解析 │\n│ Channel.tryRecv() │ │ Structured Output │\n│ session.append() │ │ 重试逻辑 │\n│ 键盘 → Action 分发 │ └──────────────────────┘\n└──────────────────────┘ ┌──────────────────────┐\n │ Executor Task │\n │ spawn 子进程 │\n │ PTY I/O 读写 │\n │ PTY agentic 循环 │\n └──────────────────────┘\n```\n\n### 屏幕状态机\n\n```\nSTART → Loading ──┬──→ Result ──┬──→ Execute ──→ Result\n │ ├──→ PTY Agentic\n ├──→ Prompt │ (交互式命令)\n │ (Investigate/Clarify/Modify)\n └──→ Error └──→ 退出 Shell\n```\n\n- **Loading** — spinner + 流式 LLM 文本实时展示\n- **Result** — 命令 + 分解说明 + 安全警告 + 选项菜单(运行/复制/修改/退出)\n- **Execute** — 普通 spawn 执行,实时滚动输出\n- **Prompt.Investigate** — 环境调查:LLM 提出诊断命令,用户逐条/批量授权\n- **Prompt.Clarify** — LLM 询问澄清\n- **Prompt.Modify** — 用户修改命令\n- **Error** — 错误展示 + 重试/退出\n\n### LLM 调用流程\n\n```\nSession.start(query)\n → RequestBuilder.build() // system prompt + context + history + JSON Schema\n → LLMProvider.chat(chatRequest) // 多态分派到具体 Provider\n → HTTP POST + Structured Output 配置\n → 响应校验(json_schema / json_object)\n → parseLLMResponseJson() → LLMResponse(Command/Investigate/Clarify)\n → Session.handleResponse() // SafetyFallback 兜底 + FactEdits 合并\n → SessionAction(ShowCommand/RequestInvestigate/AskClarify/Error)\n```\n\n### PTY Agentic 循环(交互式命令)\n\n交互式命令(`interactive: true`)走 PTY agentic 循环,核心流程:\n1. `infra/pty.cj` 创建 PTY(`/dev/ptmx` + fork + exec),设置 `TIOCSWINSZ`\n2. 非阻塞读 master fd → LLM tool-calling 分析输出(write/ask_user/wait/exit/interrupt)\n3. 写 master fd → 继续读,子进程退出则结束\n4. 节流控制:最小调用间隔 2000ms、最小新增字节 256、最大 50 轮\n\n## 源码目录结构\n\n```\nsrc/\n├── main.cj # 入口:参数解析、配置加载、TUI/调试/安装卸载分发\n├── types/ # 零依赖数据模型\n│ ├── llm.cj # LLMResponse, CommandData, InvestigateData, SafetyLevel\n│ ├── session.cj # Message, MessageRole, EnvironmentContext\n│ └── pty.cj # PtyTool, PtyLoopState, PtyResult, AppEvent, CommandResult\n├── core/ # 业务编排层\n│ ├── session.cj # Session:对话历史、LLM 交互、FactEdits 合并\n│ ├── request.cj # RequestBuilder:system prompt + context + history + JSON Schema\n│ ├── context.cj # ContextManager:环境上下文持久化和迁移入口\n│ ├── pty_agent.cj # PTY agentic 循环:PTY + LLM tool-calling + 用户确认\n│ ├── safety_fallback.cj # 本地安全兜底检查\n│ └── retry.cj # LLM 重试策略和错误分类\n├── adapters/ # LLM Provider 适配层\n│ ├── provider.cj # Provider 抽象、ChatRequest/ChatResponse、tool-calling 类型\n│ ├── openai_compat.cj # OpenAI 兼容接口\n│ ├── anthropic.cj # Anthropic Messages API\n│ ├── google.cj # Google Gemini API\n│ ├── structured_output.cj # Provider-specific structured output 参数构造\n│ ├── sse.cj # SSE 流解析器\n│ └── util.cj # Provider/SSE 共用 JSON 工具函数\n├── infra/ # OS 和持久化基础设施\n│ ├── config.cj # 配置加载、环境变量覆盖、默认配置落盘\n│ ├── paths.cj # 配置和上下文路径\n│ ├── fs.cj # 文件读写、flock 排他锁、原子写入\n│ ├── file_permissions.cj # 敏感文件 chmod 0600\n│ ├── migration.cj # context.json 版本迁移\n│ ├── spawn.cj # 普通子进程执行、输出捕获、超时和中断\n│ ├── pty.cj # PTY 创建、窗口大小、master fd 读写、子进程控制\n│ ├── pty_text_buffer.cj # PTY 输出纯文本归一化、LLM 上下文 compact/delta\n│ ├── terminal_buffer.cj # 虚拟终端缓冲区和 ANSI 解析\n│ ├── clipboard.cj # 剪贴板:OSC 52 → wl-copy → xclip → 降级展示\n│ ├── shell_rc.cj # Shell RC install/uninstall,marker 包围\n│ └── util.cj # debug trace、环境变量、进程/信号工具\n├── tui/ # 终端 UI 层\n│ ├── tui.cj # TUI 初始化、主循环、Screen 路由、Channel 接收\n│ ├── input.cj # 键盘/鼠标事件 → Action\n│ ├── signal.cj # self-pipe 信号通知辅助\n│ ├── theme.cj # 颜色和样式常量\n│ ├── screens/\n│ │ ├── loading.cj # Loading 屏\n│ │ ├── result.cj # 命令结果屏\n│ │ ├── execute.cj # 普通 spawn 执行屏\n│ │ ├── pty_agentic.cj # PTY agentic 执行屏\n│ │ ├── prompt.cj # investigate/clarify/modify 输入屏\n│ │ └── error.cj # 错误屏\n│ └── components/\n│ ├── layout.cj # 通用布局辅助\n│ ├── command_display.cj # 命令展示\n│ ├── explanation.cj # 命令分解说明\n│ ├── safety_badge.cj # 安全等级展示\n│ ├── option_menu.cj # 选项菜单\n│ ├── overlay_panel.cj # PTY overlay 面板\n│ ├── spinner.cj # 加载动画\n│ ├── styled_text.cj # 富文本样式辅助\n│ └── text_input.cj # 文本输入/敏感输入\n├── util/ # Prompt 和 schema 工具\n│ ├── prompt.cj # 主流程、json_object、PTY agent prompt\n│ └── json_schema.cj # LLMResponse JSON Schema\n├── i18n/\n│ └── i18n.cj # LANG/LC_ALL 语言检测和中英文 UI/prompt 文案\n└── tests/ # 验证测试程序包\n ├── pkg.cj\n ├── concurrency_verify/main.cj\n ├── ffi_pty_verify/main.cj\n ├── http_verify/main.cj\n ├── pty_context_verify/main.cj\n ├── pty_integration/main.cj\n └── signal_verify/main.cj\n```\n\n## 编码约定与注意事项\n\n### 仓颉语言关键规则\n\n- **`std.core` 自动导入**,无需显式 import。`String`、`Array`、`Option`、`Rune` 等均可直接用\n- **`safeUtf8(Array)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array)`\n- **`@JsonAdapter`** 宏来自 `naivejson`,为类/枚举自动生成 `serialize()`/`deserialize()`/`toJsonSchema()`\n- **`@C` 结构体** 用于 FFI 场景,如 `Winsize` 用于 `ioctl(TIOCSWINSZ)`\n- **`StringBuilder`** 用于高效拼接字符串,避免大量 `+` 产生中间字符串\n- **`unsafe {}` 块** 包裹所有 FFI 函数调用(`read`/`write`/`fork`/`ioctl`/`close`/`kill` 等)\n- **`Attribute` 用 `var` 不用 `let`**:`class` 的字段必须用 `var` 声明(即使只在构造后赋值)\n- **`spawn {}`** 创建新线程,返回 `Future`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 safeUtf8\n\n项目中禁止用 `String(Rune(Int32(byte)))` 逐字节拼接字符串;这种写法会破坏多字节 UTF-8 序列,导致中文和其他非 ASCII 输出乱码。\n\n当前 PTY LLM 文本上下文使用 `src/infra/pty_text_buffer.cj` 中的 `safeUtf8(bytes)` 做容错转换。它会保留合法 UTF-8 序列,并用替换符处理截断或非法字节,适合处理 PTY tail、delta、ANSI 剥离后的片段等不保证边界完整的终端输出:\n\n```cangjie\n// ✅ PTY/LLM 文本上下文:容错处理不完整或非法 UTF-8\nsafeUtf8(bytesRange(bytes, start, end).toArray())\n\n// ❌ 错误做法:逐字节转 Rune 会破坏多字节 UTF-8\nString(Rune(Int32(byte)))\n```\n\n已确认完整的 UTF-8 字节数组仍使用 `String.fromUtf8(Array)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option` 为 `None` 时不出现在 JSON 输出中\n- **`@JsonName[\"alt_name\"]`** 自定义字段名映射\n- LLMResponse 是 **payloaded enum(无 @JsonAdapter)**,需手动 dispatch 解析\n\n### 配置加载优先级\n\n1. Provider 专属环境变量(`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`)\n2. 通用环境变量(`LLM_API_KEY`,其次 `OPENAI_API_KEY`)\n3. `~/.config/termhelper/config.json` → `llm.api_key`\n4. 无配置时 TUI 提示\n\n### 文件锁\n\n`context.json` 读写全程使用 `flock(LOCK_EX)` 排他锁,防止多实例丢失更新。\n\n### 国际化\n\n`src/i18n/i18n.cj` 根据 `LC_ALL`/`LANG` 环境变量自动切换中英文。UI 文本通过 `uiText(key)` 获取,prompt 中的 few-shot examples 也随语言切换。\n\n### 调试跟踪\n\n`src/infra/util.cj` 提供 `debugTrace(msg)` 和 `enableDebugTrace()`。CLI 调试模式(`--debug`)会将完整 LLMResponse JSON 打印到 stdout。\n\n## 第三方依赖\n\n| 依赖 | 路径 | 说明 |\n|------|------|------|\n| stdx | `thirdparty/stdx/` | 仓颉扩展标准库 release 包,`setup-deps.sh` 按当前架构下载 |\n| ratatui | `thirdparty/ratatui/` | Rust FFI 绑定的 TUI 框架(cangjie-ratatui-sdk + cangjie-tui-ffi) |\n| naivejson | `thirdparty/naivejson/` | JSON 序列化库,提供 `@JsonAdapter` 宏和 `DataModel` 转换 |\n\n`references/` 目录包含参考实现(claude-code SDK 源码、musl libc PTY 实现、ratatui/naivejson 的本地备份)。\n\n### thirdparty 管理规则\n\n`thirdparty/` 整体被主仓库 `.gitignore` 忽略,不提交第三方源码、release 包、解压产物或 Rust target 产物。第三方依赖的获取和本地修补统一由 `scripts/setup-deps.sh` 完成:\n\n- stdx:按当前 CPU 架构下载 `cangjie_stdx` release zip,解压到 `thirdparty/stdx/_cjnative/`。\n- naivejson:clone 后 patch 其 `naivejson/cjpm.toml` 中当前架构的 stdx 路径,指向本项目 `thirdparty/stdx/.../static/stdx`。\n- ratatui:clone 后 patch SDK 的 Rust FFI link path,并构建 `cangjie-tui-ffi`。\n- 主项目:`setup-deps.sh` 会 patch 根目录 `cjpm.toml` 的 ratatui FFI `link-option`,把提交版相对路径替换为当前 checkout 的绝对路径,供本机 `cjpm build` 使用。\n\n绝对路径不应该进入提交。尤其是 `setup-deps.sh` 运行后,根目录 `cjpm.toml` 可能出现当前机器的绝对路径(例如 `-L.../thirdparty/ratatui/cangjie-tui-ffi/target/release`)。如果本轮需要修改并提交 `cjpm.toml`,提交前必须把这些本地 patch 恢复为仓库约定的相对路径;如果本轮没有修改 `cjpm.toml`,不要 add 它。提交前用以下命令检查:\n\n```bash\nrg -n \"/home/|/Users/|Documents/Apps|cangjie-tui-ffi/target/release\" cjpm.toml scripts/setup-deps.sh README.md CLAUDE.md\ngit diff --cached -- cjpm.toml\n```\n\n### ratatui SDK 注意事项\n\n`thirdparty/ratatui/` 中的 Cangjie ratatui SDK 是 `gitcode.com/Cangjie-SIG/ratatui` 的 hard fork。原始 SDK 质量低劣,包含大量 stub 代码,容易导致功能静默失败。\n\n开发时应将该 SDK 视为不可靠依赖:遇到 TUI 行为异常、FFI 返回成功但无实际渲染、事件/样式/布局接口无效等情况,不要默认业务代码有错,应主动检查并修改 `thirdparty/ratatui/`,补完 termhelper 所需功能。\n\n## Bundled Skills 使用说明\n\n本项目在 `.claude/skills/` 中绑定了仓颉语言相关 skills,Codex/Claude Code 等编码代理开发本项目时应优先使用这些资料,而不是凭经验猜测仓颉 API:\n\n- `cangjie-lang-features` — 仓颉语言核心特性\n- `cangjie-std` — 标准库常用功能速查\n- `cangjie-stdx` — 扩展标准库功能速查(JSON、HTTP、日志、压缩、TLS 等)\n- `cangjie-toolchains` — `cjc` / `cjpm` / `cjfmt` / `cjlint` 等工具链\n- `cangjie-regulations` — 仓颉项目规范和最佳实践\n- `cangjie-original-docs` — 原始文档兜底\n\n实现新功能时,优先级应为:\n\n1. 仓颉标准库 `std`。\n2. 仓颉扩展标准库 `stdx`。\n3. 项目已有 helper / infra 封装。\n4. 第三方依赖(如 `naivejson`、`ratatui`)。\n5. FFI。\n\n不要第一反应写 FFI。只有在 `std` / `stdx` / 现有封装无法满足需求,或确实需要 POSIX/终端/PTY/底层系统能力时,才新增 FFI。新增 FFI 时必须集中声明、使用 `unsafe {}` 包裹调用、处理资源释放,并优先参考本项目已有 `infra/pty.cj`、`infra/spawn.cj`、`infra/file_permissions.cj` 的写法。\n\n## Git Commit Format\n\nUse English commit messages in the format `type: description`:\n- `feat:` — new features\n- `fix:` — bug fixes\n- `refactor:` — refactoring\n- `improvement:` — improvements\n- `docs:` — documentation\n- `chore:` — maintenance\n\n**注意**: 对于 Claude Code,每个 commit message 末尾必须包含 `Co-Authored-By` trailer。模型名称需要参考运行环境提示中的 `You are powered by the model <实际的模型名称>`,不应该无脑认为自己是 `Claude Opus 4.8`。\n\n对于 Codex,每个 commit message 末尾也必须包含 `Co-Authored-By` trailer,使用当前 Codex 模型身份。例如本项目当前约定为:\n\n```text\nCo-Authored-By: GPT-5.5 \n```\n\n## Memory 文件\n\n项目相关的持久记忆存放在 `/home/chiyuki/.claude/projects/-home-chiyuki-Projects-termhelper/memory/`,启动时通过 `MEMORY.md` 索引加载。\n"}, "aad9cdd6aae9a2077cc47e2f87e41d7cc9bc7066": {"t": 1785771236.3283296, "n": 12620, "h": "67c088a8ecf4b0e3", "c": "# CLAUDE.md — termhelper\n\n> Linux 终端智能助手。用仓颉语言编写,调用大模型将自然语言转换为可执行的 Shell 命令,辅以分解说明和安全警告。\n\n## 项目概述\n\n用户在终端输入 `?? <自然语言查询>`,termhelper 启动全屏 TUI,调用 LLM 生成命令,展示命令分解说明和安全等级,让用户选择运行/复制/修改。对于交互式命令(如 `sudo apt upgrade`),支持 PTY agentic 模式让 LLM 接管终端自动交互。\n\n**核心技术栈:** 仓颉(Cangjie)1.1.0 + ratatui TUI 框架 + Rust FFI 绑定 + `stdx.net.http` HTTP 客户端\n\n## 构建与运行\n\n### 首次配置\n\n```bash\n# 1. 拉取第三方依赖(naivejson、ratatui),构建 Rust FFI\nbash scripts/setup-deps.sh\n\n# 2. 编译项目\ncjpm build\n\n# 3. 运行(需要设置 API Key)\nexport LLM_API_KEY=\"sk-xxx\"\n./target/release/bin/termhelper \"删除所有 tmp 开头的文件夹\"\n```\n\n### 日常开发\n\n```bash\ncjpm build # 编译(输出到 target/release/bin/termhelper)\ncjpm build --verbose # 查看完整编译命令\n./target/release/bin/termhelper \"查询内容\" # 构建后运行\n./target/release/bin/termhelper --help # 查看帮助\n./target/release/bin/termhelper --debug \"查询\" # CLI 调试模式,打印 LLMResponse JSON\n```\n\n当前构建将 stdx、ratatui SDK、ratatui Rust FFI 和仓颉运行时静态链接进主程序;发行版自带的系统 C/C++ 运行库仍保持动态依赖。\n\n### 配置文件\n\n`~/.config/termhelper/config.json`(自动创建,权限 0600):\n\n```jsonc\n{\n \"version\": 1,\n \"provider\": \"openai_compat\", // openai_compat | anthropic | google\n \"llm\": {\n \"api_key\": \"\", // 也可用环境变量 LLM_API_KEY / OPENAI_API_KEY\n \"base_url\": \"https://api.openai.com/v1\",\n \"model\": \"gpt-4o\",\n \"timeout_sec\": 60,\n \"structured_output_mode\": \"auto\" // auto | json_object | json_schema\n },\n \"execution\": {\n \"spawn_timeout_sec\": 300,\n \"pty_default_mode\": \"confirm\", // confirm | full_auto\n \"pty_max_rounds\": 50\n },\n \"retry\": { \"max_retries\": 3, \"base_delay_ms\": 1000, \"max_delay_ms\": 16000 }\n}\n```\n\n### LLM Provider 支持\n\n| Provider | 配置值 | Structured Output 机制 |\n|----------|--------|----------------------|\n| OpenAI 兼容(含 DeepSeek 等) | `openai_compat` | `response_format: json_schema`,不支持时降级为 `json_object` + prompt |\n| Anthropic | `anthropic` | `tool_use` 单工具(`input_schema` = response schema) |\n| Google | `google` | `response_schema` + `response_mime_type: application/json` |\n\n## 架构设计\n\n### 四层架构\n\n```\n入口 (main.cj) → TUI 层 (tui/) → 核心层 (core/) → 适配器层 (adapters/)\n ↓ → 基础设施层 (infra/)\n Channel → 工具层 (util/)\n (后台 → TUI 单向)\n ↑\n types/ (零依赖,纯数据)\n```\n\n**包依赖规则:**\n- `types/` — 零依赖,被所有包引用。定义 LLM 类型、会话模型、PTY agentic 类型、AppEvent 等\n- `core/` — 依赖 adapters 接口(不依赖具体实现)、infra、util\n- `tui/` — 依赖 core、infra\n- `adapters/` — 不依赖 core/tui(仅依赖 types)\n- `infra/` — 不依赖 core/adapters/tui(仅依赖 types)\n- `util/` — 不依赖其他业务包\n\n### 线程模型\n\n**核心原则:Session(对话历史)仅主线程持有。** 后台任务通过 Channel 单向通知 TUI。\n\n```\nMain Thread (TUI) Background Tasks\n┌──────────────────────┐ ┌──────────────────────┐\n│ ratatui 事件循环 │ Channel │ LLM Task │\n│ pollEvent + draw │◄─────────│ HTTP 请求 + SSE 解析 │\n│ Channel.tryRecv() │ │ Structured Output │\n│ session.append() │ │ 重试逻辑 │\n│ 键盘 → Action 分发 │ └──────────────────────┘\n└──────────────────────┘ ┌──────────────────────┐\n │ Executor Task │\n │ spawn 子进程 │\n │ PTY I/O 读写 │\n │ PTY agentic 循环 │\n └──────────────────────┘\n```\n\n### 屏幕状态机\n\n```\nSTART → Loading ──┬──→ Result ──┬──→ Execute ──→ Result\n │ ├──→ PTY Agentic\n ├──→ Prompt │ (交互式命令)\n │ (Investigate/Clarify/Modify)\n └──→ Error └──→ 退出 Shell\n```\n\n- **Loading** — spinner + 流式 LLM 文本实时展示\n- **Result** — 命令 + 分解说明 + 安全警告 + 选项菜单(运行/复制/修改/退出)\n- **Execute** — 普通 spawn 执行,实时滚动输出\n- **Prompt.Investigate** — 环境调查:LLM 提出诊断命令,用户逐条/批量授权\n- **Prompt.Clarify** — LLM 询问澄清\n- **Prompt.Modify** — 用户修改命令\n- **Error** — 错误展示 + 重试/退出\n\n### LLM 调用流程\n\n```\nSession.start(query)\n → RequestBuilder.build() // system prompt + context + history + JSON Schema\n → LLMProvider.chat(chatRequest) // 多态分派到具体 Provider\n → HTTP POST + Structured Output 配置\n → 响应校验(json_schema / json_object)\n → parseLLMResponseJson() → LLMResponse(Command/Investigate/Clarify)\n → Session.handleResponse() // SafetyFallback 兜底 + FactEdits 合并\n → SessionAction(ShowCommand/RequestInvestigate/AskClarify/Error)\n```\n\n### PTY Agentic 循环(交互式命令)\n\n交互式命令(`interactive: true`)走 PTY agentic 循环,核心流程:\n1. `infra/pty.cj` 创建 PTY(`/dev/ptmx` + fork + exec),设置 `TIOCSWINSZ`\n2. 非阻塞读 master fd → LLM tool-calling 分析输出(write/ask_user/wait/exit/interrupt)\n3. 写 master fd → 继续读,子进程退出则结束\n4. 节流控制:最小调用间隔 2000ms、最小新增字节 256、最大 50 轮\n\n## 源码目录结构\n\n```\nsrc/\n├── main.cj # 入口:参数解析、配置加载、TUI/调试/安装卸载分发\n├── types/ # 零依赖数据模型\n│ ├── llm.cj # LLMResponse, CommandData, InvestigateData, SafetyLevel\n│ ├── session.cj # Message, MessageRole, EnvironmentContext\n│ └── pty.cj # PtyTool, PtyLoopState, PtyResult, AppEvent, CommandResult\n├── core/ # 业务编排层\n│ ├── session.cj # Session:对话历史、LLM 交互、FactEdits 合并\n│ ├── request.cj # RequestBuilder:system prompt + context + history + JSON Schema\n│ ├── context.cj # ContextManager:环境上下文持久化和迁移入口\n│ ├── pty_agent.cj # PTY agentic 循环:PTY + LLM tool-calling + 用户确认\n│ ├── safety_fallback.cj # 本地安全兜底检查\n│ └── retry.cj # LLM 重试策略和错误分类\n├── adapters/ # LLM Provider 适配层\n│ ├── provider.cj # Provider 抽象、ChatRequest/ChatResponse、tool-calling 类型\n│ ├── openai_compat.cj # OpenAI 兼容接口\n│ ├── anthropic.cj # Anthropic Messages API\n│ ├── google.cj # Google Gemini API\n│ ├── structured_output.cj # Provider-specific structured output 参数构造\n│ ├── sse.cj # SSE 流解析器\n│ └── util.cj # Provider/SSE 共用 JSON 工具函数\n├── infra/ # OS 和持久化基础设施\n│ ├── config.cj # 配置加载、环境变量覆盖、默认配置落盘\n│ ├── paths.cj # 配置和上下文路径\n│ ├── fs.cj # 文件读写、flock 排他锁、原子写入\n│ ├── file_permissions.cj # 敏感文件 chmod 0600\n│ ├── migration.cj # context.json 版本迁移\n│ ├── spawn.cj # 普通子进程执行、输出捕获、超时和中断\n│ ├── pty.cj # PTY 创建、窗口大小、master fd 读写、子进程控制\n│ ├── pty_text_buffer.cj # PTY 输出纯文本归一化、LLM 上下文 compact/delta\n│ ├── terminal_buffer.cj # 虚拟终端缓冲区和 ANSI 解析\n│ ├── clipboard.cj # 剪贴板:OSC 52 → wl-copy → xclip → 降级展示\n│ ├── shell_rc.cj # Shell RC install/uninstall,marker 包围\n│ └── util.cj # debug trace、环境变量、进程/信号工具\n├── tui/ # 终端 UI 层\n│ ├── tui.cj # TUI 初始化、主循环、Screen 路由、Channel 接收\n│ ├── input.cj # 键盘/鼠标事件 → Action\n│ ├── signal.cj # self-pipe 信号通知辅助\n│ ├── theme.cj # 颜色和样式常量\n│ ├── screens/\n│ │ ├── loading.cj # Loading 屏\n│ │ ├── result.cj # 命令结果屏\n│ │ ├── execute.cj # 普通 spawn 执行屏\n│ │ ├── pty_agentic.cj # PTY agentic 执行屏\n│ │ ├── prompt.cj # investigate/clarify/modify 输入屏\n│ │ └── error.cj # 错误屏\n│ └── components/\n│ ├── layout.cj # 通用布局辅助\n│ ├── command_display.cj # 命令展示\n│ ├── explanation.cj # 命令分解说明\n│ ├── safety_badge.cj # 安全等级展示\n│ ├── option_menu.cj # 选项菜单\n│ ├── overlay_panel.cj # PTY overlay 面板\n│ ├── spinner.cj # 加载动画\n│ ├── styled_text.cj # 富文本样式辅助\n│ └── text_input.cj # 文本输入/敏感输入\n├── util/ # Prompt 和 schema 工具\n│ ├── prompt.cj # 主流程、json_object、PTY agent prompt\n│ └── json_schema.cj # LLMResponse JSON Schema\n├── i18n/\n│ └── i18n.cj # LANG/LC_ALL 语言检测和中英文 UI/prompt 文案\n└── tests/ # 验证测试程序包\n ├── pkg.cj\n ├── concurrency_verify/main.cj\n ├── ffi_pty_verify/main.cj\n ├── http_verify/main.cj\n ├── pty_context_verify/main.cj\n ├── pty_integration/main.cj\n └── signal_verify/main.cj\n```\n\n## 编码约定与注意事项\n\n### 仓颉语言关键规则\n\n- **`std.core` 自动导入**,无需显式 import。`String`、`Array`、`Option`、`Rune` 等均可直接用\n- **`safeUtf8(Array)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array)`\n- **`@JsonAdapter`** 宏来自 `naivejson`,为类/枚举自动生成 `serialize()`/`deserialize()`/`toJsonSchema()`\n- **`@C` 结构体** 用于 FFI 场景,如 `Winsize` 用于 `ioctl(TIOCSWINSZ)`\n- **`StringBuilder`** 用于高效拼接字符串,避免大量 `+` 产生中间字符串\n- **`unsafe {}` 块** 包裹所有 FFI 函数调用(`read`/`write`/`fork`/`ioctl`/`close`/`kill` 等)\n- **`Attribute` 用 `var` 不用 `let`**:`class` 的字段必须用 `var` 声明(即使只在构造后赋值)\n- **`spawn {}`** 创建新线程,返回 `Future`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 safeUtf8\n\n项目中禁止用 `String(Rune(Int32(byte)))` 逐字节拼接字符串;这种写法会破坏多字节 UTF-8 序列,导致中文和其他非 ASCII 输出乱码。\n\n当前 PTY LLM 文本上下文使用 `src/infra/pty_text_buffer.cj` 中的 `safeUtf8(bytes)` 做容错转换。它会保留合法 UTF-8 序列,并用替换符处理截断或非法字节,适合处理 PTY tail、delta、ANSI 剥离后的片段等不保证边界完整的终端输出:\n\n```cangjie\n// ✅ PTY/LLM 文本上下文:容错处理不完整或非法 UTF-8\nsafeUtf8(bytesRange(bytes, start, end).toArray())\n\n// ❌ 错误做法:逐字节转 Rune 会破坏多字节 UTF-8\nString(Rune(Int32(byte)))\n```\n\n已确认完整的 UTF-8 字节数组仍使用 `String.fromUtf8(Array)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option` 为 `None` 时不出现在 JSON 输出中\n- **`@JsonName[\"alt_name\"]`** 自定义字段名映射\n- LLMResponse 是 **payloaded enum(无 @JsonAdapter)**,需手动 dispatch 解析\n\n### 配置加载优先级\n\n1. Provider 专属环境变量(`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`)\n2. 通用环境变量(`LLM_API_KEY`,其次 `OPENAI_API_KEY`)\n3. `~/.config/termhelper/config.json` → `llm.api_key`\n4. 无配置时 TUI 提示\n\n### 文件锁\n\n`context.json` 读写全程使用 `flock(LOCK_EX)` 排他锁,防止多实例丢失更新。\n\n### 国际化\n\n`src/i18n/i18n.cj` 根据 `LC_ALL`/`LANG` 环境变量自动切换中英文。UI 文本通过 `uiText(key)` 获取,prompt 中的 few-shot examples 也随语言切换。\n\n### 调试跟踪\n\n`src/infra/util.cj` 提供 `debugTrace(msg)` 和 `enableDebugTrace()`。CLI 调试模式(`--debug`)会将完整 LLMResponse JSON 打印到 stdout。\n\n## 第三方依赖\n\n| 依赖 | 路径 | 说明 |\n|------|------|------|\n| ratatui | `thirdparty/ratatui/` | Rust FFI 绑定的 TUI 框架(cangjie-ratatui-sdk + cangjie-tui-ffi) |\n| naivejson | `thirdparty/naivejson/` | JSON 序列化库,提供 `@JsonAdapter` 宏和 `DataModel` 转换 |\n\n`references/` 目录包含参考实现(claude-code SDK 源码、musl libc PTY 实现、ratatui/naivejson 的本地备份)。\n\n### ratatui SDK 注意事项\n\n`thirdparty/ratatui/` 中的 Cangjie ratatui SDK 是 `gitcode.com/Cangjie-SIG/ratatui` 的 hard fork。原始 SDK 质量低劣,包含大量 stub 代码,容易导致功能静默失败。\n\n开发时应将该 SDK 视为不可靠依赖:遇到 TUI 行为异常、FFI 返回成功但无实际渲染、事件/样式/布局接口无效等情况,不要默认业务代码有错,应主动检查并修改 `thirdparty/ratatui/`,补完 termhelper 所需功能。\n\n## Bundled Skills 使用说明\n\n本项目在 `.claude/skills/` 中绑定了仓颉语言相关 skills,Codex/Claude Code 等编码代理开发本项目时应优先使用这些资料,而不是凭经验猜测仓颉 API:\n\n- `cangjie-lang-features` — 仓颉语言核心特性\n- `cangjie-std` — 标准库常用功能速查\n- `cangjie-stdx` — 扩展标准库功能速查(JSON、HTTP、日志、压缩、TLS 等)\n- `cangjie-toolchains` — `cjc` / `cjpm` / `cjfmt` / `cjlint` 等工具链\n- `cangjie-regulations` — 仓颉项目规范和最佳实践\n- `cangjie-original-docs` — 原始文档兜底\n\n实现新功能时,优先级应为:\n\n1. 仓颉标准库 `std`。\n2. 仓颉扩展标准库 `stdx`。\n3. 项目已有 helper / infra 封装。\n4. 第三方依赖(如 `naivejson`、`ratatui`)。\n5. FFI。\n\n不要第一反应写 FFI。只有在 `std` / `stdx` / 现有封装无法满足需求,或确实需要 POSIX/终端/PTY/底层系统能力时,才新增 FFI。新增 FFI 时必须集中声明、使用 `unsafe {}` 包裹调用、处理资源释放,并优先参考本项目已有 `infra/pty.cj`、`infra/spawn.cj`、`infra/file_permissions.cj` 的写法。\n\n## Git Commit Format\n\nUse English commit messages in the format `type: description`:\n- `feat:` — new features\n- `fix:` — bug fixes\n- `refactor:` — refactoring\n- `improvement:` — improvements\n- `docs:` — documentation\n- `chore:` — maintenance\n\n**注意**: 对于 Claude Code,每个 commit message 末尾必须包含 `Co-Authored-By` trailer。模型名称需要参考运行环境提示中的 `You are powered by the model <实际的模型名称>`,不应该无脑认为自己是 `Claude Opus 4.8`。\n\n对于 Codex,每个 commit message 末尾也必须包含 `Co-Authored-By` trailer,使用当前 Codex 模型身份。例如本项目当前约定为:\n\n```text\nCo-Authored-By: GPT-5.5 \n```\n\n## Memory 文件\n\n项目相关的持久记忆存放在 `/home/chiyuki/.claude/projects/-home-chiyuki-Projects-termhelper/memory/`,启动时通过 `MEMORY.md` 索引加载。\n"}, "43676b75827dfe00e968575f59c270ac1f690876": {"t": 1785771237.6660533, "n": 10160, "h": "2f681d0fe2089ead", "c": "# CLAUDE.md — termhelper\n\n> Linux 终端智能助手。用仓颉语言编写,调用大模型将自然语言转换为可执行的 Shell 命令,辅以分解说明和安全警告。\n\n## 项目概述\n\n用户在终端输入 `?? <自然语言查询>`,termhelper 启动全屏 TUI,调用 LLM 生成命令,展示命令分解说明和安全等级,让用户选择运行/复制/修改。对于交互式命令(如 `sudo apt upgrade`),支持 PTY agentic 模式让 LLM 接管终端自动交互。\n\n**核心技术栈:** 仓颉(Cangjie)1.1.0 + ratatui TUI 框架 + Rust FFI 绑定 + `stdx.net.http` HTTP 客户端\n\n## 构建与运行\n\n### 首次配置\n\n```bash\n# 1. 拉取第三方依赖(naivejson、ratatui),构建 Rust FFI\nbash scripts/setup-deps.sh\n\n# 2. 编译项目\ncjpm build\n\n# 3. 运行(需要设置 API Key)\nexport LLM_API_KEY=\"sk-xxx\"\n./target/release/bin/termhelper \"删除所有 tmp 开头的文件夹\"\n```\n\n### 日常开发\n\n```bash\ncjpm build # 编译(输出到 target/release/bin/termhelper)\ncjpm build --verbose # 查看完整编译命令\n./target/release/bin/termhelper \"查询内容\" # 构建后运行\n./target/release/bin/termhelper --help # 查看帮助\n./target/release/bin/termhelper --tui-demo # 彩色样式预览\n./target/release/bin/termhelper --debug \"查询\" # CLI 调试模式,打印 LLMResponse JSON\n```\n\n当前构建将 stdx、ratatui SDK、ratatui Rust FFI 和仓颉运行时静态链接进主程序;发行版自带的系统 C/C++ 运行库仍保持动态依赖。\n\n### 配置文件\n\n`~/.config/termhelper/config.json`(自动创建,权限 0600):\n\n```jsonc\n{\n \"version\": 1,\n \"provider\": \"openai_compat\", // openai_compat | anthropic | google\n \"llm\": {\n \"api_key\": \"\", // 也可用环境变量 LLM_API_KEY / OPENAI_API_KEY\n \"base_url\": \"https://api.openai.com/v1\",\n \"model\": \"gpt-4o\",\n \"timeout_sec\": 60,\n \"structured_output_mode\": \"auto\" // auto | json_object | json_schema\n },\n \"execution\": {\n \"spawn_timeout_sec\": 300,\n \"pty_default_mode\": \"confirm\", // confirm | full_auto\n \"pty_max_rounds\": 50\n },\n \"retry\": { \"max_retries\": 3, \"base_delay_ms\": 1000, \"max_delay_ms\": 16000 }\n}\n```\n\n### LLM Provider 支持\n\n| Provider | 配置值 | Structured Output 机制 |\n|----------|--------|----------------------|\n| OpenAI 兼容(含 DeepSeek 等) | `openai_compat` | `response_format: json_schema`,不支持时降级为 `json_object` + prompt |\n| Anthropic | `anthropic` | `tool_use` 单工具(`input_schema` = response schema) |\n| Google | `google` | `response_schema` + `response_mime_type: application/json` |\n\n## 架构设计\n\n### 四层架构\n\n```\n入口 (main.cj) → TUI 层 (tui/) → 核心层 (core/) → 适配器层 (adapters/)\n ↓ → 基础设施层 (infra/)\n Channel → 工具层 (util/)\n (后台 → TUI 单向)\n ↑\n types/ (零依赖,纯数据)\n```\n\n**包依赖规则:**\n- `types/` — 零依赖,被所有包引用。定义 LLM 类型、会话模型、PTY agentic 类型、AppEvent 等\n- `core/` — 依赖 adapters 接口(不依赖具体实现)、infra、util\n- `tui/` — 依赖 core、infra\n- `adapters/` — 不依赖 core/tui(仅依赖 types)\n- `infra/` — 不依赖 core/adapters/tui(仅依赖 types)\n- `util/` — 不依赖其他业务包\n\n### 线程模型\n\n**核心原则:Session(对话历史)仅主线程持有。** 后台任务通过 Channel 单向通知 TUI。\n\n```\nMain Thread (TUI) Background Tasks\n┌──────────────────────┐ ┌──────────────────────┐\n│ ratatui 事件循环 │ Channel │ LLM Task │\n│ pollEvent + draw │◄─────────│ HTTP 请求 + SSE 解析 │\n│ Channel.tryRecv() │ │ Structured Output │\n│ session.append() │ │ 重试逻辑 │\n│ 键盘 → Action 分发 │ └──────────────────────┘\n└──────────────────────┘ ┌──────────────────────┐\n │ Executor Task │\n │ spawn 子进程 │\n │ PTY I/O 读写 │\n │ PTY agentic 循环 │\n └──────────────────────┘\n```\n\n### 屏幕状态机\n\n```\nSTART → Loading ──┬──→ Result ──┬──→ Execute ──→ Result\n │ ├──→ PTY Agentic\n ├──→ Prompt │ (交互式命令)\n │ (Investigate/Clarify/Modify)\n └──→ Error └──→ 退出 Shell\n```\n\n- **Loading** — spinner + 流式 LLM 文本实时展示\n- **Result** — 命令 + 分解说明 + 安全警告 + 选项菜单(运行/复制/修改/退出)\n- **Execute** — 普通 spawn 执行,实时滚动输出\n- **Prompt.Investigate** — 环境调查:LLM 提出诊断命令,用户逐条/批量授权\n- **Prompt.Clarify** — LLM 询问澄清\n- **Prompt.Modify** — 用户修改命令\n- **Error** — 错误展示 + 重试/退出\n\n### LLM 调用流程\n\n```\nSession.start(query)\n → RequestBuilder.build() // system prompt + context + history + JSON Schema\n → LLMProvider.chat(chatRequest) // 多态分派到具体 Provider\n → HTTP POST + Structured Output 配置\n → 响应校验(json_schema / json_object)\n → parseLLMResponseJson() → LLMResponse(Command/Investigate/Clarify)\n → Session.handleResponse() // SafetyFallback 兜底 + FactEdits 合并\n → SessionAction(ShowCommand/RequestInvestigate/AskClarify/Error)\n```\n\n### PTY Agentic 循环(交互式命令)\n\n交互式命令(`interactive: true`)走 PTY agentic 循环,核心流程:\n1. `infra/pty.cj` 创建 PTY(`/dev/ptmx` + fork + exec),设置 `TIOCSWINSZ`\n2. 非阻塞读 master fd → LLM tool-calling 分析输出(write/ask_user/wait/exit/interrupt)\n3. 写 master fd → 继续读,子进程退出则结束\n4. 节流控制:最小调用间隔 2000ms、最小新增字节 256、最大 50 轮\n\n## 源码目录结构\n\n```\nsrc/\n├── main.cj # 入口:参数解析、配置加载、TUI 启动\n├── types/\n│ ├── llm.cj # LLMResponse, CommandData, InvestigateData, SafetyLevel\n│ ├── session.cj # Message, MessageRole, EnvironmentContext\n│ └── pty.cj # PtyTool, PtyLoopState, PtyResult, AppEvent, CommandResult\n├── tui/\n│ ├── tui.cj # TUI 初始化、主循环、Screen 路由、Channel 接收、渲染\n│ ├── input.cj # 键盘事件 → Action 转换(含鼠标滚轮)\n│ ├── theme.cj # 颜色常量定义\n│ ├── screens/ # 各屏幕的渲染函数(纯文本 + ratatui styled 各一套)\n│ └── components/ # 可复用组件(spinner、option_menu、text_input 等)\n├── core/\n│ ├── session.cj # Session:对话历史管理、LLM 交互编排(仅主线程)\n│ ├── context.cj # ContextManager:环境上下文持久化(JSON + flock 排他锁)\n│ ├── request.cj # RequestBuilder:构造 LLM 请求\n│ ├── safety_fallback.cj # LLM 未标记危险时的本地兜底检查\n│ └── retry.cj # LLM 重试策略(指数退避 + HTTP 状态码分类)\n├── adapters/\n│ ├── provider.cj # LLMProvider 多态分派 + ChatRequest/ChatResponse/StreamEvent\n│ ├── openai_compat.cj # OpenAI 协议兼容实现\n│ ├── anthropic.cj # Anthropic 协议实现\n│ ├── google.cj # Google 协议实现\n│ ├── sse.cj # SSE 协议解析器\n│ └── structured_output.cj # 各 Provider 的 Structured Output 适配封装\n├── infra/\n│ ├── pty.cj # PTY 基础设施:openpt/fork/exec/master fd 读写\n│ ├── spawn.cj # 普通子进程 spawn(stdout/stderr 捕获 + 超时 + 可中断)\n│ ├── config.cj # 配置加载(env → JSON → 自动创建)\n│ ├── clipboard.cj # 剪贴板(OSC 52 → wl-copy → xclip → 降级展示)\n│ ├── shell_rc.cj # Shell RC 管理(install/uninstall,注释标记包围)\n│ ├── fs.cj # 文件工具(JSON 读写 + flock LOCK_EX + 原子写入)\n│ ├── migration.cj # ContextMigration:逐版本数据迁移\n│ ├── paths.cj # 路径常量(~/.config/termhelper/)\n│ ├── util.cj # 调试跟踪、环境变量获取\n│ └── terminal_buffer.cj # 虚拟终端缓冲区(ANSI 解析 + 单元格渲染)\n├── util/\n│ ├── prompt.cj # System prompt 模板(json_schema 模式 / json_object 模式 / PTY agent)\n│ └── json_schema.cj # LLM 响应的 JSON Schema 定义(用于 Structured Output)\n├── i18n/\n│ └── i18n.cj # 国际化:根据 LANG/LC_ALL 自动切换中英文\n└── tests/ # 验证测试(FFI PTY、HTTP、并发、信号)\n```\n\n## 编码约定与注意事项\n\n### 仓颉语言关键规则\n\n- **`std.core` 自动导入**,无需显式 import。`String`、`Array`、`Option`、`Rune` 等均可直接用\n- **`String.fromUtf8(Array)`** — 正确的 UTF-8 字节数组 → 字符串转换方式。**禁止** `String(Rune(Int32(byte)))` 逐字节转换(会破坏多字节 UTF-8 序列导致乱码)\n- **`@JsonAdapter`** 宏来自 `naivejson`,为类/枚举自动生成 `serialize()`/`deserialize()`/`toJsonSchema()`\n- **`@C` 结构体** 用于 FFI 场景,如 `Winsize` 用于 `ioctl(TIOCSWINSZ)`\n- **`StringBuilder`** 用于高效拼接字符串,避免大量 `+` 产生中间字符串\n- **`unsafe {}` 块** 包裹所有 FFI 函数调用(`read`/`write`/`fork`/`ioctl`/`close`/`kill` 等)\n- **`Attribute` 用 `var` 不用 `let`**:`class` 的字段必须用 `var` 声明(即使只在构造后赋值)\n- **`spawn {}`** 创建新线程,返回 `Future`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 UTF-8\n\n**这是最近修复的关键 bug。** 项目中从命令输出读取原始字节后,必须使用 `String.fromUtf8(Array)` 进行批量 UTF-8 解码:\n\n```cangjie\n// ✅ 正确做法(src/infra/spawn.cj 中的 ptrToUtf8 函数)\nlet arr = Array(n, repeat: 0)\nfor (i in 0..n) { arr[i] = ptr.read(i) }\nString.fromUtf8(arr)\n\n// ❌ 错误做法——会导致非 ASCII 字符乱码\nString(Rune(Int32(byte)))\n```\n\n涉及文件:`src/infra/spawn.cj`(`ptrToUtf8` 辅助函数 + 4 处调用点)、`src/infra/pty.cj`(`tryRead` 方法)。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option` 为 `None` 时不出现在 JSON 输出中\n- **`@JsonName[\"alt_name\"]`** 自定义字段名映射\n- LLMResponse 是 **payloaded enum(无 @JsonAdapter)**,需手动 dispatch 解析\n\n### 配置加载优先级\n\n1. Provider 专属环境变量(`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`)\n2. 通用环境变量(`LLM_API_KEY`,其次 `OPENAI_API_KEY`)\n3. `~/.config/termhelper/config.json` → `llm.api_key`\n4. 无配置时 TUI 提示\n\n### 文件锁\n\n`context.json` 读写全程使用 `flock(LOCK_EX)` 排他锁,防止多实例丢失更新。\n\n### 国际化\n\n`src/i18n/i18n.cj` 根据 `LC_ALL`/`LANG` 环境变量自动切换中英文。UI 文本通过 `uiText(key)` 获取,prompt 中的 few-shot examples 也随语言切换。\n\n### 调试跟踪\n\n`src/infra/util.cj` 提供 `debugTrace(msg)` 和 `enableDebugTrace()`。CLI 调试模式(`--debug`)会将完整 LLMResponse JSON 打印到 stdout。\n\n## 待实现的功能(根据架构文档)\n\n架构文档(`docs/architecture-final.md`)中规划但尚未完整实现的功能:\n\n- **PTY Agentic 全屏渲染** — 虚拟终端 ANSI 解析 + ratatui overlay 面板(当前 execute 屏仅简单 spawn)\n- **信号处理** — SIGWINCH 终端大小变化、SIGINT/SIGTERM 优雅退出、panic hook\n- **`--install` / `--uninstall`** — Shell 集成安装/卸载(当前为占位实现)\n- **流式 LLM 文本展示** — Loading 屏实时展示 LLM 生成内容(当前 Loading 屏为基本实现)\n- **PTY 敏感输入处理** — `AskUser(sensitive=true)` 的密码遮罩和脱敏\n- **Structured Output 降级链** — `json_schema` → `json_object` + prompt 的自动降级(已有框架但可能未完整测试)\n\n## 第三方依赖\n\n| 依赖 | 路径 | 说明 |\n|------|------|------|\n| ratatui | `thirdparty/ratatui/` | Rust FFI 绑定的 TUI 框架(cangjie-ratatui-sdk + cangjie-tui-ffi) |\n| naivejson | `thirdparty/naivejson/` | JSON 序列化库,提供 `@JsonAdapter` 宏和 `DataModel` 转换 |\n\n`references/` 目录包含参考实现(claude-code SDK 源码、musl libc PTY 实现、ratatui/naivejson 的本地备份)。\n\n## Git 提交格式\n\n项目使用中文提交信息,格式为 `type: description`:\n- `feat:` — 新功能\n- `fix:` — 修复\n- `refactor:` — 重构\n- `improvement:` — 改进/优化\n- `docs:` — 文档\n- `chore:` — 杂务\n\n提交末尾加 `Co-Authored-By: Claude Opus 4.8 `。\n\n## Memory 文件\n\n项目相关的持久记忆存放在 `/home/chiyuki/.claude/projects/-home-chiyuki-Projects-termhelper/memory/`,启动时通过 `MEMORY.md` 索引加载。\n"}, "83b339bb0c956f37d4b466ea5bee44a929e25461": {"t": 1785771238.9061024, "n": 10146, "h": "1f84f8f708abf379", "c": "# CLAUDE.md — termhelper\n\n> Linux 终端智能助手。用仓颉语言编写,调用大模型将自然语言转换为可执行的 Shell 命令,辅以分解说明和安全警告。\n\n## 项目概述\n\n用户在终端输入 `?? <自然语言查询>`,termhelper 启动全屏 TUI,调用 LLM 生成命令,展示命令分解说明和安全等级,让用户选择运行/复制/修改。对于交互式命令(如 `sudo apt upgrade`),支持 PTY agentic 模式让 LLM 接管终端自动交互。\n\n**核心技术栈:** 仓颉(Cangjie)1.1.0 + ratatui TUI 框架 + Rust FFI 绑定 + `stdx.net.http` HTTP 客户端\n\n## 构建与运行\n\n### 首次配置\n\n```bash\n# 1. 拉取第三方依赖(naivejson、ratatui),构建 Rust FFI\nbash scripts/setup-deps.sh\n\n# 2. 编译项目\ncjpm build\n\n# 3. 运行(需要设置 API Key)\nexport LLM_API_KEY=\"sk-xxx\"\nbash scripts/run.sh \"删除所有 tmp 开头的文件夹\"\n```\n\n### 日常开发\n\n```bash\ncjpm build # 编译(输出到 target/release/bin/termhelper)\ncjpm build --verbose # 查看完整编译命令\nbash scripts/run.sh \"查询内容\" # 构建后运行\nbash scripts/run.sh --help # 查看帮助\nbash scripts/run.sh --tui-demo # 彩色样式预览\nbash scripts/run.sh --debug \"查询\" # CLI 调试模式,打印 LLMResponse JSON\n```\n\n**`scripts/run.sh` 做了什么:** 设置 `LD_LIBRARY_PATH`(指向仓颉 stdx 动态库 + ratatui Rust FFI 的 `.so`),然后执行 `target/release/bin/termhelper`。\n\n### 配置文件\n\n`~/.config/termhelper/config.json`(自动创建,权限 0600):\n\n```jsonc\n{\n \"version\": 1,\n \"provider\": \"openai_compat\", // openai_compat | anthropic | google\n \"llm\": {\n \"api_key\": \"\", // 也可用环境变量 LLM_API_KEY / OPENAI_API_KEY\n \"base_url\": \"https://api.openai.com/v1\",\n \"model\": \"gpt-4o\",\n \"timeout_sec\": 60,\n \"structured_output_mode\": \"auto\" // auto | json_object | json_schema\n },\n \"execution\": {\n \"spawn_timeout_sec\": 300,\n \"pty_default_mode\": \"confirm\", // confirm | full_auto\n \"pty_max_rounds\": 50\n },\n \"retry\": { \"max_retries\": 3, \"base_delay_ms\": 1000, \"max_delay_ms\": 16000 }\n}\n```\n\n### LLM Provider 支持\n\n| Provider | 配置值 | Structured Output 机制 |\n|----------|--------|----------------------|\n| OpenAI 兼容(含 DeepSeek 等) | `openai_compat` | `response_format: json_schema`,不支持时降级为 `json_object` + prompt |\n| Anthropic | `anthropic` | `tool_use` 单工具(`input_schema` = response schema) |\n| Google | `google` | `response_schema` + `response_mime_type: application/json` |\n\n## 架构设计\n\n### 四层架构\n\n```\n入口 (main.cj) → TUI 层 (tui/) → 核心层 (core/) → 适配器层 (adapters/)\n ↓ → 基础设施层 (infra/)\n Channel → 工具层 (util/)\n (后台 → TUI 单向)\n ↑\n types/ (零依赖,纯数据)\n```\n\n**包依赖规则:**\n- `types/` — 零依赖,被所有包引用。定义 LLM 类型、会话模型、PTY agentic 类型、AppEvent 等\n- `core/` — 依赖 adapters 接口(不依赖具体实现)、infra、util\n- `tui/` — 依赖 core、infra\n- `adapters/` — 不依赖 core/tui(仅依赖 types)\n- `infra/` — 不依赖 core/adapters/tui(仅依赖 types)\n- `util/` — 不依赖其他业务包\n\n### 线程模型\n\n**核心原则:Session(对话历史)仅主线程持有。** 后台任务通过 Channel 单向通知 TUI。\n\n```\nMain Thread (TUI) Background Tasks\n┌──────────────────────┐ ┌──────────────────────┐\n│ ratatui 事件循环 │ Channel │ LLM Task │\n│ pollEvent + draw │◄─────────│ HTTP 请求 + SSE 解析 │\n│ Channel.tryRecv() │ │ Structured Output │\n│ session.append() │ │ 重试逻辑 │\n│ 键盘 → Action 分发 │ └──────────────────────┘\n└──────────────────────┘ ┌──────────────────────┐\n │ Executor Task │\n │ spawn 子进程 │\n │ PTY I/O 读写 │\n │ PTY agentic 循环 │\n └──────────────────────┘\n```\n\n### 屏幕状态机\n\n```\nSTART → Loading ──┬──→ Result ──┬──→ Execute ──→ Result\n │ ├──→ PTY Agentic\n ├──→ Prompt │ (交互式命令)\n │ (Investigate/Clarify/Modify)\n └──→ Error └──→ 退出 Shell\n```\n\n- **Loading** — spinner + 流式 LLM 文本实时展示\n- **Result** — 命令 + 分解说明 + 安全警告 + 选项菜单(运行/复制/修改/退出)\n- **Execute** — 普通 spawn 执行,实时滚动输出\n- **Prompt.Investigate** — 环境调查:LLM 提出诊断命令,用户逐条/批量授权\n- **Prompt.Clarify** — LLM 询问澄清\n- **Prompt.Modify** — 用户修改命令\n- **Error** — 错误展示 + 重试/退出\n\n### LLM 调用流程\n\n```\nSession.start(query)\n → RequestBuilder.build() // system prompt + context + history + JSON Schema\n → LLMProvider.chat(chatRequest) // 多态分派到具体 Provider\n → HTTP POST + Structured Output 配置\n → 响应校验(json_schema / json_object)\n → parseLLMResponseJson() → LLMResponse(Command/Investigate/Clarify)\n → Session.handleResponse() // SafetyFallback 兜底 + FactEdits 合并\n → SessionAction(ShowCommand/RequestInvestigate/AskClarify/Error)\n```\n\n### PTY Agentic 循环(交互式命令)\n\n交互式命令(`interactive: true`)走 PTY agentic 循环,核心流程:\n1. `infra/pty.cj` 创建 PTY(`/dev/ptmx` + fork + exec),设置 `TIOCSWINSZ`\n2. 非阻塞读 master fd → LLM tool-calling 分析输出(write/ask_user/wait/exit/interrupt)\n3. 写 master fd → 继续读,子进程退出则结束\n4. 节流控制:最小调用间隔 2000ms、最小新增字节 256、最大 50 轮\n\n## 源码目录结构\n\n```\nsrc/\n├── main.cj # 入口:参数解析、配置加载、TUI 启动\n├── types/\n│ ├── llm.cj # LLMResponse, CommandData, InvestigateData, SafetyLevel\n│ ├── session.cj # Message, MessageRole, EnvironmentContext\n│ └── pty.cj # PtyTool, PtyLoopState, PtyResult, AppEvent, CommandResult\n├── tui/\n│ ├── tui.cj # TUI 初始化、主循环、Screen 路由、Channel 接收、渲染\n│ ├── input.cj # 键盘事件 → Action 转换(含鼠标滚轮)\n│ ├── theme.cj # 颜色常量定义\n│ ├── screens/ # 各屏幕的渲染函数(纯文本 + ratatui styled 各一套)\n│ └── components/ # 可复用组件(spinner、option_menu、text_input 等)\n├── core/\n│ ├── session.cj # Session:对话历史管理、LLM 交互编排(仅主线程)\n│ ├── context.cj # ContextManager:环境上下文持久化(JSON + flock 排他锁)\n│ ├── request.cj # RequestBuilder:构造 LLM 请求\n│ ├── safety_fallback.cj # LLM 未标记危险时的本地兜底检查\n│ └── retry.cj # LLM 重试策略(指数退避 + HTTP 状态码分类)\n├── adapters/\n│ ├── provider.cj # LLMProvider 多态分派 + ChatRequest/ChatResponse/StreamEvent\n│ ├── openai_compat.cj # OpenAI 协议兼容实现\n│ ├── anthropic.cj # Anthropic 协议实现\n│ ├── google.cj # Google 协议实现\n│ ├── sse.cj # SSE 协议解析器\n│ └── structured_output.cj # 各 Provider 的 Structured Output 适配封装\n├── infra/\n│ ├── pty.cj # PTY 基础设施:openpt/fork/exec/master fd 读写\n│ ├── spawn.cj # 普通子进程 spawn(stdout/stderr 捕获 + 超时 + 可中断)\n│ ├── config.cj # 配置加载(env → JSON → 自动创建)\n│ ├── clipboard.cj # 剪贴板(OSC 52 → wl-copy → xclip → 降级展示)\n│ ├── shell_rc.cj # Shell RC 管理(install/uninstall,注释标记包围)\n│ ├── fs.cj # 文件工具(JSON 读写 + flock LOCK_EX + 原子写入)\n│ ├── migration.cj # ContextMigration:逐版本数据迁移\n│ ├── paths.cj # 路径常量(~/.config/termhelper/)\n│ ├── util.cj # 调试跟踪、环境变量获取\n│ └── terminal_buffer.cj # 虚拟终端缓冲区(ANSI 解析 + 单元格渲染)\n├── util/\n│ ├── prompt.cj # System prompt 模板(json_schema 模式 / json_object 模式 / PTY agent)\n│ └── json_schema.cj # LLM 响应的 JSON Schema 定义(用于 Structured Output)\n├── i18n/\n│ └── i18n.cj # 国际化:根据 LANG/LC_ALL 自动切换中英文\n└── tests/ # 验证测试(FFI PTY、HTTP、并发、信号)\n```\n\n## 编码约定与注意事项\n\n### 仓颉语言关键规则\n\n- **`std.core` 自动导入**,无需显式 import。`String`、`Array`、`Option`、`Rune` 等均可直接用\n- **`String.fromUtf8(Array)`** — 正确的 UTF-8 字节数组 → 字符串转换方式。**禁止** `String(Rune(Int32(byte)))` 逐字节转换(会破坏多字节 UTF-8 序列导致乱码)\n- **`@JsonAdapter`** 宏来自 `naivejson`,为类/枚举自动生成 `serialize()`/`deserialize()`/`toJsonSchema()`\n- **`@C` 结构体** 用于 FFI 场景,如 `Winsize` 用于 `ioctl(TIOCSWINSZ)`\n- **`StringBuilder`** 用于高效拼接字符串,避免大量 `+` 产生中间字符串\n- **`unsafe {}` 块** 包裹所有 FFI 函数调用(`read`/`write`/`fork`/`ioctl`/`close`/`kill` 等)\n- **`Attribute` 用 `var` 不用 `let`**:`class` 的字段必须用 `var` 声明(即使只在构造后赋值)\n- **`spawn {}`** 创建新线程,返回 `Future`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 UTF-8\n\n**这是最近修复的关键 bug。** 项目中从命令输出读取原始字节后,必须使用 `String.fromUtf8(Array)` 进行批量 UTF-8 解码:\n\n```cangjie\n// ✅ 正确做法(src/infra/spawn.cj 中的 ptrToUtf8 函数)\nlet arr = Array(n, repeat: 0)\nfor (i in 0..n) { arr[i] = ptr.read(i) }\nString.fromUtf8(arr)\n\n// ❌ 错误做法——会导致非 ASCII 字符乱码\nString(Rune(Int32(byte)))\n```\n\n涉及文件:`src/infra/spawn.cj`(`ptrToUtf8` 辅助函数 + 4 处调用点)、`src/infra/pty.cj`(`tryRead` 方法)。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option` 为 `None` 时不出现在 JSON 输出中\n- **`@JsonName[\"alt_name\"]`** 自定义字段名映射\n- LLMResponse 是 **payloaded enum(无 @JsonAdapter)**,需手动 dispatch 解析\n\n### 配置加载优先级\n\n1. Provider 专属环境变量(`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`)\n2. 通用环境变量(`LLM_API_KEY`,其次 `OPENAI_API_KEY`)\n3. `~/.config/termhelper/config.json` → `llm.api_key`\n4. 无配置时 TUI 提示\n\n### 文件锁\n\n`context.json` 读写全程使用 `flock(LOCK_EX)` 排他锁,防止多实例丢失更新。\n\n### 国际化\n\n`src/i18n/i18n.cj` 根据 `LC_ALL`/`LANG` 环境变量自动切换中英文。UI 文本通过 `uiText(key)` 获取,prompt 中的 few-shot examples 也随语言切换。\n\n### 调试跟踪\n\n`src/infra/util.cj` 提供 `debugTrace(msg)` 和 `enableDebugTrace()`。CLI 调试模式(`--debug`)会将完整 LLMResponse JSON 打印到 stdout。\n\n## 待实现的功能(根据架构文档)\n\n架构文档(`docs/architecture-final.md`)中规划但尚未完整实现的功能:\n\n- **PTY Agentic 全屏渲染** — 虚拟终端 ANSI 解析 + ratatui overlay 面板(当前 execute 屏仅简单 spawn)\n- **信号处理** — SIGWINCH 终端大小变化、SIGINT/SIGTERM 优雅退出、panic hook\n- **`--install` / `--uninstall`** — Shell 集成安装/卸载(当前为占位实现)\n- **流式 LLM 文本展示** — Loading 屏实时展示 LLM 生成内容(当前 Loading 屏为基本实现)\n- **PTY 敏感输入处理** — `AskUser(sensitive=true)` 的密码遮罩和脱敏\n- **Structured Output 降级链** — `json_schema` → `json_object` + prompt 的自动降级(已有框架但可能未完整测试)\n\n## 第三方依赖\n\n| 依赖 | 路径 | 说明 |\n|------|------|------|\n| ratatui | `thirdparty/ratatui/` | Rust FFI 绑定的 TUI 框架(cangjie-ratatui-sdk + cangjie-tui-ffi) |\n| naivejson | `thirdparty/naivejson/` | JSON 序列化库,提供 `@JsonAdapter` 宏和 `DataModel` 转换 |\n\n`references/` 目录包含参考实现(claude-code SDK 源码、musl libc PTY 实现、ratatui/naivejson 的本地备份)。\n\n## Git 提交格式\n\n项目使用中文提交信息,格式为 `type: description`:\n- `feat:` — 新功能\n- `fix:` — 修复\n- `refactor:` — 重构\n- `improvement:` — 改进/优化\n- `docs:` — 文档\n- `chore:` — 杂务\n\n提交末尾加 `Co-Authored-By: Claude Opus 4.8 `。\n\n## Memory 文件\n\n项目相关的持久记忆存放在 `/home/chiyuki/.claude/projects/-home-chiyuki-Projects-termhelper/memory/`,启动时通过 `MEMORY.md` 索引加载。\n"}}}}