Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chiyuki0325_termhelper.json
T

1 line
105 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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<AppEvent> → 工具层 (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<AppEvent> 单向通知 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<UInt8>)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array<UInt8>)`\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<T>`。用于后台 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<UInt8>)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable<DataModel>` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option<T>` 为 `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/<arch>_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 <codex@openai.com>\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<AppEvent> → 工具层 (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<AppEvent> 单向通知 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<UInt8>)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array<UInt8>)`\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<T>`。用于后台 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<UInt8>)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable<DataModel>` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option<T>` 为 `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/<arch>_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 <codex@openai.com>\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<AppEvent> → 工具层 (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<AppEvent> 单向通知 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<UInt8>)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array<UInt8>)`\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<T>`。用于后台 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<UInt8>)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable<DataModel>` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option<T>` 为 `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/<arch>_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 <codex@openai.com>\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<AppEvent> → 工具层 (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<AppEvent> 单向通知 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<UInt8>)`** — PTY/终端输出进入 LLM 文本上下文时使用的容错 UTF-8 转换。已确认完整 UTF-8 的普通文件/HTTP/JSON 字节数组仍可使用 `String.fromUtf8(Array<UInt8>)`\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<T>`。用于后台 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<UInt8>)`,例如配置/RC 文件、SSE 行缓冲、JSON 字符串拼装等。命令 stdout/stderr 和 PTY 读取仍需注意 chunk 边界;如要把片段送入 LLM 上下文,优先经过 `PtyLlmContextBuffer` / `safeUtf8`。\n\n### JSON 序列化\n\n项目使用 `naivejson` 库(thirdparty)进行 JSON 序列化:\n- 带 `@JsonAdapter` 的类型自动实现 `Serializable<DataModel>` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option<T>` 为 `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 <codex@openai.com>\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<AppEvent> → 工具层 (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<AppEvent> 单向通知 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<UInt8>)`** — 正确的 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<T>`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 UTF-8\n\n**这是最近修复的关键 bug。** 项目中从命令输出读取原始字节后,必须使用 `String.fromUtf8(Array<UInt8>)` 进行批量 UTF-8 解码:\n\n```cangjie\n// ✅ 正确做法(src/infra/spawn.cj 中的 ptrToUtf8 函数)\nlet arr = Array<UInt8>(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<DataModel>` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option<T>` 为 `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 <noreply@anthropic.com>`。\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<AppEvent> → 工具层 (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<AppEvent> 单向通知 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<UInt8>)`** — 正确的 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<T>`。用于后台 LLM 调用、子进程执行\n\n### 字符串与 UTF-8\n\n**这是最近修复的关键 bug。** 项目中从命令输出读取原始字节后,必须使用 `String.fromUtf8(Array<UInt8>)` 进行批量 UTF-8 解码:\n\n```cangjie\n// ✅ 正确做法(src/infra/spawn.cj 中的 ptrToUtf8 函数)\nlet arr = Array<UInt8>(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<DataModel>` 和 `JsonSchema` 接口\n- **`@JsonIgnore`** 标注运行时字段(如 `DiagnosticCommand.authorized`),不参与序列化\n- **`@JsonIgnoreNull`** 使得 `Option<T>` 为 `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 <noreply@anthropic.com>`。\n\n## Memory 文件\n\n项目相关的持久记忆存放在 `/home/chiyuki/.claude/projects/-home-chiyuki-Projects-termhelper/memory/`,启动时通过 `MEMORY.md` 索引加载。\n"}}}}