简介:这份资源是 Anthropic 官方 Claude Code CLI 工具的源码逆向还原项目,面向希望深入理解该 CLI 工作原理、并需要可运行可调试工程环境的开发者。项目以 TypeScript 为主,类型问题已全面修复,具备企业级可靠性,依赖 lock 文件保真,可直接执行 bun i 安装、bun run dev 启动,便于本地调试与二次开发。压缩包共约 2000 个文件,其中 1617 个 ts 与 245 个 tsx 构成核心源码与组件,另有 83 个 md 文档、26 个 js、18 个 json 及少量配置与脚本文件,整体约 50.86MB,目录结构完整,覆盖工具链、运行时类型与各类功能模块。目前已有 132 人学习。读者可借此研究 Claude Code 的工程化实现、类型组织与模块划分,并基于主干代码扩展自己的 CLI 应用。
1. 原汁原味 Claude Code 可运行版:为什么值得自己搭一遍
很多人第一次接触 Claude Code,是在别人的终端录屏里看它自动改文件、跑测试、提交 commit,感觉像个黑匣子。但真到自己机器上,往往卡在第一步:装不上、跑不起来、改不动。市面上流传的所谓「可运行版」大多是打包好的二进制,出了问题只能等更新,连日志都看不全。而这份 TypeScript 源码版的价值恰恰在于——它把整个 Agent 循环、工具调用协议、上下文管理逻辑摊开给你看,你可以断点调试、可以改 prompt 模板、可以加自己的工具。对于想搞清楚「AI 编程助手到底怎么调度工具」的工程师来说,这比看一百篇原理文章都管用。这篇文章面向的是愿意动手的开发者:你需要有 Node.js 基础、用过 TypeScript、能看懂 npm 脚本。我会从环境准备讲到构建调试,再到实际改造,每一步都给出可复现的命令和参数说明。搭完这一遍,你手里就有个能改、能断点、能扩展的 Claude Code 工作副本。
2. 把源码跑起来:环境、依赖与首次构建
2.1 为什么选源码构建而不是直接装二进制
直接下载安装包确实快,但你会失去三样东西:第一,看不到工具调用的完整请求体,排查「为什么它没读那个文件」时只能猜;第二,改不了系统提示词,想让它遵守团队编码规范只能靠外部配置绕;第三,版本升级不可控,某天自动更新后行为变了,你连回滚的余地都没有。源码构建的代价是首次配置大概二十分钟,换来的是完全可控。常见做法是 fork 一份到自己的仓库,锁定依赖版本,后续按需合并上游改动。我一般会在项目根目录加一个.nvmrc固定 Node 版本,避免团队里有人用 18 有人用 22 导致构建产物不一致。
2.2 环境准备:Node、包管理器与 TypeScript 版本
先确认基础环境。Claude Code 这类 Agent 工具对 Node 版本有要求,低于 18 会在 ESM 加载和 fetch 上翻车。执行下面命令检查:
node -v # 期望 v18.17.0 或更高,推荐 v20 LTS npm -v # 期望 9.x 以上 corepack enable # 启用 pnpm/yarn 的 shim,很多源码包用 pnpm如果node -v低于 18,用 nvm 切换:
nvm install 20 nvm use 20 nvm alias default 20包管理器优先看源码根目录有没有pnpm-lock.yaml。有就用 pnpm,没有再用 npm。混用包管理器是依赖树错乱的常见原因,血泪经验:node_modules里出现两份不同版本的同一依赖,构建时报的类型错误能让你查半天。
TypeScript 版本要留意。热搜里提到的moduleResolution=node10已弃用、baseUrl将在 TS 7.0 停止支持,这些不是危言耸听。如果你的源码tsconfig.json里还在用node10,构建会打警告,未来直接报错。建议改成:
{ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES2022", "strict": true, "skipLibCheck": true } }moduleResolution设为NodeNext后,相对导入需要带.js后缀(即使源文件是.ts),这是 ESM 的硬性要求,改的时候别漏。skipLibCheck打开能跳过第三方库的类型检查,省大量构建时间,代价是库自身的类型错误你看不到,权衡后一般建议开。
2.3 拉取依赖与首次构建的完整命令
环境就绪后,进入源码目录按顺序执行:
git clone <你的仓库地址> claude-code-src cd claude-code-src pnpm install --frozen-lockfile # 严格按 lock 文件装,避免版本漂移 pnpm run build # 多数项目对应 tsc 或 tsup--frozen-lockfile的作用是:如果package.json和 lock 文件不一致就直接失败,而不是偷偷升级依赖。CI 环境必加,本地首次也可以加,确认依赖干净。
构建脚本因项目而异,常见几种:
| 脚本命令 | 实际动作 | 产物位置 |
|---|---|---|
tsc -p tsconfig.build.json | 纯类型编译 | dist/ |
tsup src/index.ts | 打包 + 压缩 | dist/index.js |
esbuild --bundle | 极速打包 | 自定义 |
构建失败时先看第一个错误,不要被后面几十条连锁报错带偏。九成情况是类型不匹配或缺少.js后缀。如果报Cannot find module './xxx.js',检查源文件是不是xxx.ts且导入写成了./xxx——NodeNext 下必须写./xxx.js。
2.4 配置 API 接入与首次运行验证
构建成功后,需要配置模型接入。Claude Code 走的是 Anthropic 的 API 协议,你需要准备 API Key 和可访问的端点。配置一般通过环境变量注入:
export ANTHROPIC_API_KEY="你的key" export ANTHROPIC_BASE_URL="你的端点地址" # 若使用兼容网关注意:环境变量写进 shell 配置文件前,确认该文件权限是 600,别让 key 泄露给同机器其他用户。
然后运行入口:
node dist/index.js --help能打印帮助信息,说明构建产物可执行。接着做一次最小对话验证:
node dist/index.js -p "列出当前目录的文件"-p是单次执行模式,跑完即退,适合脚本化验证。如果卡住不动,先看网络能否到达端点,再看 key 是否有效。首次跑通后,建议把这条命令写进package.json的 scripts,命名为smoke,每次改完代码先跑一遍,比手动敲省事。
3. 调试与改造:让源码真正为你所用
3.1 用 VS Code 断点调试 Agent 主循环
跑起来只是第一步,能断点才是源码版的核心价值。在项目根目录建.vscode/launch.json:
{ "version": "0.2.0", "configurations": [ { "name": "Debug Claude Code", "type": "node", "request": "launch", "program": "${workspaceFolder}/dist/index.js", "args": ["-p", "读取 package.json 并总结依赖"], "env": { "ANTHROPIC_API_KEY": "${env:ANTHROPIC_API_KEY}" }, "sourceMaps": true, "outFiles": ["${workspaceFolder}/dist/**/*.js"] } ] }关键参数是sourceMaps和outFiles。前者让你在.ts源文件上打断点,后者告诉调试器去哪里找编译产物。前提是构建时开了sourceMap: true。如果断点变成灰色空心圆,说明 sourcemap 没生成或路径不对,检查tsconfig里的sourceMap和outDir。
我一般会在工具调用的分发函数上下断点,比如处理read_file、write_file的地方。这样能清楚看到模型返回的 tool_use 块长什么样、参数怎么解析、结果怎么回填。这是理解 Agent 循环最快的方式,比读文档直观得多。
3.2 修改系统提示词与工具注册表
系统提示词通常单独放在一个文件里,比如src/prompts/system.ts或prompts/system.md。改这里能直接影响模型行为。比如团队要求所有代码注释用中文,可以在提示词里加一条约束。改完重新构建即可生效,不需要动其他逻辑。
工具注册表一般在src/tools/index.ts,结构类似:
export const tools = [ readFileTool, writeFileTool, bashTool, // 新增工具在这里注册 ];每个工具是一个对象,包含name、description、inputSchema和execute方法。description会被塞进请求发给模型,写得越清楚,模型调用越准。想加一个自定义工具,照着现有工具复制一份改就行。注意inputSchema用 JSON Schema 描述参数,模型靠它理解怎么传参。schema 写错会导致模型传参格式不对,execute 里直接抛错。
3.3 构建产物瘦身与启动速度优化
源码构建的产物往往偏大,启动慢。几个可调的优化点:第一,tsup或esbuild配置里开minify和treeshake,去掉未引用代码;第二,把不常用的工具做成动态导入,用到才加载;第三,检查有没有把整个node_modules打进去,正常应该只打自己的源码,依赖走外部 require。
# 用 esbuild 分析产物构成 npx esbuild-visualizer --metadata metafile.json启动速度还受 Node 冷启动影响。如果每次调用都要等一两秒,可以考虑用--enable-source-maps之外的方式,比如把常用路径预热。不过对交互式使用来说,这点延迟通常可接受,别过度优化。
3.4 用环境变量切换多套配置
开发时经常要在不同端点、不同 key 之间切换。硬编码或反复改 shell 很烦。做法是建几个 env 文件:
# .env.dev ANTHROPIC_API_KEY=dev_key ANTHROPIC_BASE_URL=https://dev-endpoint # .env.prod ANTHROPIC_API_KEY=prod_key ANTHROPIC_BASE_URL=https://prod-endpoint运行时用dotenv-cli加载:
npx dotenv-cli -e .env.dev -- node dist/index.js -p "测试"这样切换环境只改一个参数,不用动代码。记得把.env.*加进.gitignore,key 进仓库是重大事故。
4. 避坑与排查:源码构建最容易翻车的五个地方
4.1 现象:构建报一堆「Cannot find module」,原因:ESM 后缀缺失
NodeNext 模式下,所有相对导入必须带.js后缀。从 CommonJS 迁过来的代码几乎必踩。现象是tsc报几十条找不到模块,但文件明明存在。解决:全局搜索from './和from '../,把没有后缀的补上.js。注意只补相对路径,包名不用。批量改可以用正则替换,但改完务必跑一次构建确认。
4.2 现象:运行时报「ERR_REQUIRE_ESM」,原因:CJS 与 ESM 混用
某个依赖是纯 ESM 包,你的代码却用require引入。现象是启动直接崩,堆栈指向require()。解决:把该处改成import,或者用动态await import()。如果整个项目是 CJS,考虑在package.json里加"type": "module"切到 ESM,但要评估所有依赖是否兼容。混用是长期维护的噩梦,尽早统一。
4.3 现象:断点不生效,原因:sourcemap 路径错位
VS Code 里断点是灰色,或者断在编译后的 JS 上。原因通常是outDir和launch.json里的outFiles对不上,或者构建时没开 sourcemap。解决:确认tsconfig里sourceMap: true、outDir: "./dist",launch.json里outFiles指向dist/**/*.js。如果用了 tsup,检查它的 sourcemap 选项是否开启。
4.4 现象:工具调用参数解析失败,原因:JSON Schema 写得不严谨
模型返回的 tool_use 参数偶尔缺字段或类型不对,execute 里直接抛错。根因是inputSchema没写required或类型约束太松。解决:给每个必填参数加进required数组,类型用string、number明确标注,枚举值用enum限定。schema 越严格,模型传参越规范。另外 execute 里要做防御性校验,别假设模型一定传对。
4.5 现象:改了提示词没生效,原因:构建缓存或产物未更新
改完system.ts重新运行,行为没变。先确认构建真的跑了,dist里的文件时间戳是不是新的。tsup 有缓存,必要时加--no-cache。还有一种情况是提示词被硬编码在多个地方,你只改了一处。全局搜索提示词里的特征句,确认没有遗漏。改完跑一次smoke脚本验证。
5. 进阶技巧:把 Claude Code 源码改造成团队专属助手
走到这一步,你已经能构建、调试、改工具了。接下来最有价值的改造方向是「团队适配」。我一般会做三件事。第一,把团队的编码规范、目录约定、提交信息格式写进系统提示词,让模型生成的代码天然符合规范,省掉 review 时的反复拉扯。第二,加一个「项目上下文工具」,让它启动时自动读取README、CONTRIBUTING和最近的 git log,这样它对新人的问题回答得更准。第三,做一个「危险操作二次确认」的包装层,在bashTool执行rm、git push --force这类命令前拦截,要求显式确认。
验证改造是否成功,别只看它能不能跑,要看三个指标:任务完成率、工具调用次数、人工干预频率。我习惯用一个简单的对照实验:同一批任务,改造前后各跑十次,记录成功次数和平均轮次。改造有效的标志是成功次数上升、轮次下降。如果轮次反而涨了,说明提示词写得太啰嗦,模型在反复确认。
最后一个具体技巧:把常用的调试命令固化成一个Makefile,比如make build、make smoke、make debug。团队里每个人敲的命令一致,环境差异导致的问题会少很多。我自己踩过的最大坑是早期没固定 Node 版本,同事用 16 我用 20,同一个 commit 他构建失败我成功,查了一下午才发现是版本问题。从那以后,.nvmrc和engines字段成了我每个项目的标配。希望帮到你。
本文还有配套的精品资源,点击获取