一个可运行的、教学级的 Claude Code 精简复刻 :在终端里运行的 AI 编程助手(CLI Agent)。 用 TypeScript + Bun 编写,不依赖任何模型 SDK ,直接用 fetch 调 HTTP / SSE。
它对照 Claude Code 的分层结构,从零实现了一个 Agent 的核心骨架:流式对话循环、工具调用、权限闸门、上下文压缩、会话持久化、子助手、MCP 接入、容错重试。目标是让你读完代码就能理解"Claude Code 这类工具到底是怎么转起来的"。
- 命令名:
wonder-cc - 数据目录:
.wonder-cc/ - 项目资料文件:
AGENTS.md(自动注入系统提示,类似 CLAUDE.md)
特性
- 多模型适配:内置 Anthropic 与 OpenAI 兼容两套适配器。DeepSeek、OpenAI 官方、以及任意 OpenAI 兼容网关都可用;通过环境变量切换,自动探测。
- 流式对话:SSE 逐字输出,带思考转圈、工具过程、底部状态栏。
- 工具系统 :
read_file/write_file/edit_text/bash/TodoWrite/Task,外加 MCP 外部工具。 - 三级权限闸门 :只读命令直接放行;写操作征求同意;
rm -rf /、sudo、curl|bash等危险命令任何模式都拦截。支持"这次允许 / 永远允许 / 拒绝",规则可跨会话记忆。 - 上下文工程:粗估 token、三档水位(正常 / 偏满 / 危险),危险水位自动把旧对话压缩成摘要,保留最近几轮原文。
- 会话持久化 :JSONL 转录逐条追加(崩溃不丢历史),
/resume从压缩快照恢复;命令历史、用量记账、全局偏好都落盘。 - 子助手(Task) :主助手可派活给独立上下文的子助手,三工种
explore(只读探索)/plan(方案规划)/code(动手实现),中间过程不占主对话。 - MCP 客户端 :通过 stdio + JSON-RPC 接入外部工具生态,工具名统一为
mcp__<server>__<tool>。 - 斜杠命令 :
/init自动扫描项目生成 AGENTS.md、/resume、/compact、/usage、权限切换等。 - @提及文件 :输入里写
@src/foo.ts,自动读取文件内容附进消息。 - 容错重试:429 / 5xx / 网络抖动按指数退避自动重试,401 / 400 等立即报错;流式开始后不重试(避免文字重复打印)。
技术栈
| 项 | 选择 |
|---|---|
| 运行时 / 包管理 / 测试 | Bun(bun run / bun test,自动加载 .env) |
| 语言 | TypeScript(ESM) |
| 模型接入 | 原生 fetch + SSE,无 SDK |
| 终端交互 | node:readline/promises + ANSI 转义码(自绘 spinner / 状态栏) |
| 子进程 | node:child_process spawn(bash 工具、MCP server) |
| 持久化 | JSONL 追加写 + JSON 档案 |
架构分层
目录结构对照 Claude Code 布局:
bash
src/
├── entrypoints/cli.ts # 薄入口:终端 REPL,只做"显示"与"提问"
├── main.ts # 启动编排:装配 config → adapter → tools → 系统提示 → 权限
├── query.ts # 核心对话循环(async generator,产出事件,不碰界面)
├── context.ts # 系统提示拼装(身份/环境/工具清单/规矩 + AGENTS.md)
├── Tool.ts # Tool 接口定义(说明书 + execute)
├── config/environment.ts # 环境变量读取与服务商自动探测
├── constants/product.ts # 产品级常量
├── services/
│ ├── api/
│ │ ├── adapter.ts # 模型适配器(Anthropic / OpenAI 兼容)+ SSE 解析
│ │ ├── types.ts # 内部统一"普通话"类型(Message / ContentBlock)
│ │ ├── errors.ts # ApiError + 指数退避重试
│ │ └── usage.ts # 用量记账与成本估算
│ ├── mcp/
│ │ ├── client.ts # MCP stdio 客户端(JSON-RPC 2.0)
│ │ └── loader.ts # MCP 工具加载与包装
│ ├── session.ts # JSONL 会话转录(meta/message/snapshot)
│ └── task.ts # 子助手(sub-agent)
├── tools/ # 内置工具 + 权限闸门 + 路径防护
│ ├── permission.ts # 命令分级(safe/write/danger)与闸门
│ ├── paths.ts # safePath:防 ../ 越界与符号链接逃逸
│ ├── readFile.ts / writeFile.ts / editText.ts / bash.ts / todoWrite.ts / task.ts
├── query/
│ ├── tokens.ts # token 粗估
│ ├── tokenBudget.ts # 三档水位
│ └── compression.ts # 上下文压缩
├── bootstrap/
│ ├── paths.ts # 数据目录约定
│ ├── state.ts # 全局档案 state.json
│ └── history.ts # 命令历史 history.log
├── commands/
│ ├── registry.ts # 斜杠命令登记
│ ├── init.ts # /init 生成 AGENTS.md
│ └── mentions.ts # @提及文件展开
└── ui/
├── display.ts # 终端渲染(颜色/工具过程/状态栏)
└── spinner.ts # 思考转圈
核心设计 :内核与界面解耦。query() 是一个 async generator,只 yield 事件 (assistant_text_delta / tool_start / tool_result / usage / compressing / done), 不打印任何东西;CLI 层消费事件来渲染。权限闸门也不直接提问,而是通过 ToolContext.ask 回调把问题抛给上层(终端弹窗 / 子助手场景自动拒绝)。
安装与运行
需要 Bun ≥ 1.0。
bash
# 1. 安装依赖(本项目零运行时依赖,这步主要是建 Bun 环境)
bun install
# 2. 配置模型密钥(二选一或多选)
cp .env.example .env
# 编辑 .env 填入 DEEPSEEK_API_KEY 等
# 3. 直接运行
bun start
# 或
bun run src/entrypoints/cli.ts
想在任意目录用 wonder-cc 启动,可加一个 shell 别名(入口是 .ts,需由 Bun 执行):
bash
alias wonder-cc='bun run /绝对路径/wonder-claude-code/src/entrypoints/cli.ts'
模型配置
所有配置通过环境变量(写进 .env 或 export 到 shell 均可)。
服务商与密钥
| 环境变量 | 说明 | 默认值 |
|---|---|---|
DEEPSEEK_API_KEY |
DeepSeek 密钥 | --- |
DEEPSEEK_BASE_URL |
DeepSeek 地址 | https://api.deepseek.com |
DEEPSEEK_MODEL |
模型名 | deepseek-chat(推理模型可用 deepseek-reasoner) |
OPENAI_API_KEY |
OpenAI 官方 / 兼容网关密钥 | --- |
OPENAI_BASE_URL |
OpenAI 地址 | https://api.openai.com/v1 |
OPENAI_MODEL |
模型名 | gpt-4o-mini |
ANTHROPIC_API_KEY |
Anthropic 密钥 | --- |
ANTHROPIC_BASE_URL |
Anthropic 地址 | https://api.anthropic.com |
ANTHROPIC_MODEL |
模型名 | claude-sonnet-4-20250514 |
自动探测规则(未显式指定时):
- 若设置了
OPENAI_API_KEY→ 优先用 OpenAI; - 否则若有
DEEPSEEK_API_KEY→ 用 DeepSeek; - 否则若有
ANTHROPIC_API_KEY→ 用 Anthropic。
DeepSeek 与 OpenAI 走同一套 OpenAI 兼容协议;baseURL 支持裸域名、以 /v1 结尾、 或已含 /chat/completions 三种写法(会自动补齐路径)。
其他环境变量
| 环境变量 | 说明 |
|---|---|
WONDER_PROVIDER |
强制指定服务商:deepseek / openai / anthropic |
WONDER_CONTEXT_WINDOW |
覆盖上下文窗口大小(token 数,教学/测试用) |
WONDER_PERMISSION_MODE |
启动时的权限模式:default / acceptEdits / bypassPermissions |
WONDER_MCP_SERVERS |
MCP server 列表,格式见下 |
使用
启动后直接输入问题即可。模型会自己决定是否调用工具(读文件、跑命令、改代码等)。
斜杠命令
| 命令 | 作用 |
|---|---|
/init |
派探索子助手扫描项目,生成 AGENTS.md(已存在会先确认覆盖) |
/resume |
列出历史会话,选号恢复(从压缩快照继续) |
/compact |
手动压缩当前上下文 |
/usage |
查看上下文占用水位与累计 token / 估算成本 |
/mode |
查看当前权限模式 |
/default |
切换到默认模式(写 / 危险操作征求同意) |
/acceptEdits |
自动允许文件编辑(bash 写命令仍会问) |
/bypass |
跳过所有权限确认(谨慎使用) |
/help |
显示帮助 |
/exit(或 /quit、exit、quit) |
退出(自动保存命令历史) |
内置工具
| 工具 | 能力 | 权限 |
|---|---|---|
read_file |
读文本文件(超 4 万字符截断) | 只读,免确认 |
write_file |
新建 / 覆盖 / 追加文件(自动建父目录) | write 级 |
edit_text |
精确小改:replace(要求原文唯一匹配)或 insert(按行号插入) |
write 级 |
bash |
执行 shell 命令(30s 超时,输出截断 2 万字符) | 按命令分级 |
TodoWrite |
维护多步骤待办清单(落盘 .wonder-cc/todos.json) |
纯记录,免确认 |
Task |
派活给子助手 | 子助手内部再走闸门 |
权限模式与命令分级
- safe :
ls/cat/grep/git status等只读命令,直接执行。 - write :
rm/mv/ 安装依赖 /git commit/push/ 输出重定向等,默认征求同意。 - danger :
rm -rf /、sudo、mkfs、curl ... | bash、fork 炸弹等,任何模式都拦截确认。
询问时可选 y(这次允许)/ a(以后同类都允许,规则落盘记忆)/ n(拒绝,默认)。
子助手(Task)
主助手可调用 Task 工具,把活儿外包给独立上下文的子助手:
explore:代码探索,只给只读工具,绝不改文件;plan:方案规划,只读 + 待办清单,产出分步计划;code:动手实现,给全套工具,继承主会话权限闸门。
子助手的中间读文件 / 搜索过程通过进度行汇报、不刷爆主对话,最终只回传一份结论。
@提及文件
在输入里写 @相对路径,程序会自动读取该文件并把内容包成 <file path="..."> 块附在消息末尾:
bash
帮我看看 @src/query.ts 这个循环有没有问题
路径必须在工作目录内(../ 越界会被拒绝);单个文件超 2 万字符截断;读不到会提示而不报错。
MCP 外部工具
通过环境变量配置 stdio 类型的 MCP server(逗号分隔多台,等号左边是名字、右边是启动命令):
bash
export WONDER_MCP_SERVERS='mock=bun scripts/mock-mcp-server.ts, fs=npx -y @modelcontextprotocol/server-filesystem .'
启动时会自动握手(initialize → tools/list),把外部工具包装成 mcp__<server>__<tool>。 MCP 工具一律按 write 级过权限闸门;某台 server 连不上只跳过并提示,不影响启动。
数据目录
所有落盘数据都在工作目录下的 .wonder-cc/:
python
.wonder-cc/
├── state.json # 全局档案:权限模式、"永远允许"规则、最近会话 id
├── history.log # 命令历史(每行一条,上下箭头翻阅)
├── usage.jsonl # 用量记账(每次模型请求一行,含估算成本)
├── todos.json # TodoWrite 待办清单
└── projects/<工作目录编码>/
└── <session-id>.jsonl # 会话转录(meta / message / snapshot 三种行)
会话转录采用追加写 :每条消息、每次压缩快照写一行 JSON,写到一半崩溃也不丢历史。 压缩后会写一个 snapshot 作为恢复点,/resume 时从最后一个快照起叠加后续消息,跳过冗长旧历史。
成本单价是教学用的粗略参考价(人民币 / 百万 token),会随官方调价过时,真实计费以厂商账单为准。
测试与脚本
bash
bun test # 运行单元测试(适配器翻译 / 权限分级 / token 预算 / 重试 / @提及 / 会话转录)
bun run smoke # 冒烟脚本
scripts/ 下还有端到端验证脚本(需要配置真实密钥):
e2e-stage1/2/3-*.ts:最小闭环、真实 DeepSeek 流式工具调用、上下文压缩;e2e-stage5-persistence.ts:持久化与"重启记忆"验证;e2e-stage6-extensions.ts:MCP 全链路 + 子助手;mock-mcp-server.ts:最小 MCP server(提供 echo / add 两个工具),供测试接入。
与真实 Claude Code 的差距(教学简化点)
这是一个骨架级教学实现,刻意省略了大量工程:
- 文件编辑是字符串替换(
edit_text),非 AST / unified diff 补丁; - 无图片 / 多模态、无 plan-mode、hooks、自定义斜杠命令文件、设置 UI;
- MCP 只实现 stdio 传输与 tools 能力(无 resources / prompts / SSE 传输);
- 权限是正则黑名单,非静态分析;token 为经验公式粗估,非官方分词器;
- 无流式中断 / 编辑、无多会话并行、无命令自动补全。
源码地址:github.com/yzsunlei/wo...
配套学习文档
guides/ 目录下有按主题分章的学习笔记(part-01 ~ part-12 + 术语表 / 源码地图 / 阅读指南), 对照本项目的实现讲解 Claude Code 的各个子系统。
第一部分 · 认识它
| 篇 | 内容 |
|---|---|
| 第 1 篇 · 先认识它 | 它是什么、四种露面方式、靠什么跑起来、代码仓库导览 |
| 第 2 篇 · 启动的秘密 | 敲下命令后的一瞬间、快速通道、命令参数管理、全局小本本 |
第二部分 · 核心机制
| 篇 | 内容 |
|---|---|
| 第 3 篇 · 一问一答怎么转起来 | 对话循环、流式输出、长对话编排、聊天记录格式、任务中断 |
| 第 4 篇 · 工具:AI 的手 | 工具是什么、随身工具箱、为什么不一次给全、危险操作把关、排队与打断 |
| 第 5 篇 · 每次提问都塞了什么资料 | 十几份背景资料、省钱的缓存、字数预算、自动划重点、记忆、图片语音 |
第三部分 · 界面与连接
| 篇 | 内容 |
|---|---|
| 第 6 篇 · 终端里的图形界面 | 终端怎么画图、用做网页的思路做界面、自动排版、只改变化的地方、键鼠事件 |
| 第 7 篇 · 连接外面的世界 | 人脑、出错自救、外接能力、编辑器指挥、手机远程控制 |
第四部分 · 数据与扩展
| 篇 | 内容 |
|---|---|
| 第 8 篇 · 数据放哪、钱怎么算 | 两个记事本、对话找回、后悔药、记账 |
| 第 9 篇 · 怎么给它加功能 | 斜杠命令、关键时刻插一脚、插件、派小弟并行干活 |
第五部分 · 高级与工程
| 篇 | 内容 |
|---|---|
| 第 10 篇 · 高级能力 | 后台常驻、放手让它自己干、几种工作模式、语音与看屏幕 |
| 第 11 篇 · 工程上的讲究 | 一份代码变多个版本、打包拆分、性能优化、怎么测试 |
| 第 12 篇 · 串起来 | 完整走一遍修 bug 全过程、十五个关键设计选择、出问题怎么查、和同类工具的思路差异 |