简介:Claude Code 是一个在技术社区中备受关注的开源项目,其完整源码现已打包提供,适合前端、Node.js 方向的初中高级开发者以及希望掌握大型项目工程化实践的学习者。资源包为 zip 格式,共 1903 个文件,压缩后仅约 9.43MB,其中包含 1332 个 .ts 模块、552 个 .tsx 组件文件,以及 18 个 .js 辅助文件与 1 份 Markdown 文档,目录划分清晰,便于按需查阅。目前已有 289 人学习下载。源码内部体现了模块化与面向对象设计的优秀范例,核心算法、数据处理逻辑、工具类与辅助函数均配有细致注释;复杂功能被拆分为职责单一的小模块,并配有系统详尽的测试用例,展示了如何通过 Git 管理协作与追踪变更。随包附带的开发者指南、API 文档和用户手册,还可帮助读者快速完成部署、定制与二次开发,并从中获得软件质量保障、代码复用与团队协作的经验。
1. Claude Code 源码是什么:能读、能改、能换模型的终端编程助手
用了一段时间 Claude Code 的人,迟早会撞上同一个困惑:它每次执行命令前都要问一遍「是否允许」,我想让它按我的规则批量跑,官方的设置项却不够细;又或者,我刚在文档里看到某个行为说明,真机验证却发现对不上。这时候最直接的解法不是翻文档,而是去读 Claude Code 源码。官方早已把源码公开,主线就是那个 npm 包@anthropic-ai/claude-code的实现。读它你能搞清楚三件事:agent 循环怎么转、工具调用链怎么接、权限拦截在哪一步。搞懂这三件事,你就能改它的行为,接第三方模型,甚至把它拆成自己项目里的自动化引擎。这篇文章适合两类人:被内置权限模型卡住、想深度定制的从业者,以及想搞明白终端 AI 编程助手内部机制、准备二次开发的人。
2. 源码仓库结构与入口:从 cli.js 到 agent 循环的完整脉络
2.1 入口三件套:cli 解析、harness 运行时与 agent 会话
拿到源码先别急着搜「agent」关键词,先把入口理清。Claude Code 的 CLI 实现大体分三层:最外层是 cli 入口,负责解析命令行参数、读环境变量、加载配置文件;中间一层是 harness,这是整个运行时的心脏,管工具注册、权限判定、消息组装;最里层是 agent,管跟模型 API 的会话轮转。我见过不少人一上来就翻 tools 目录,看完还是一头雾水,就是因为没先建立「入口 → 运行时 → 会话」这条主线。
cli 入口做的事情非常朴素:把你敲的命令行参数映射成配置对象。比如--permission-mode会映射到权限模式,--model会映射到模型名,-p或--print会切到非交互输出。这些参数解析完,才轮到 harness 出场。harness 拿到配置后的第一件事是构建 system prompt,把工具列表、工作目录、权限规则全部拼进去,然后进入主循环。
agent 层则是最容易被误读的部分。很多人以为 agent 就是「调用 API 的那个函数」,实际上 agent 更像一个状态机,负责维护 messages 数组、判断什么时候把 assistant 消息追加回去、什么时候把 tool_result 回填。源码里这三层职责分得很清:cli 不碰业务逻辑,harness 不直接发 HTTP 请求,agent 不做权限判断。如果你要在源码里动手脚,先确定你要改的是哪一层,否则很容易改错文件后找不到生效点。
2.2 tools 注册表:源码里最值钱的一张表
工具系统的设计,决定了 Claude Code 扩展性的上限。源码里每个工具都是一个独立模块,导出固定的四个字段:name、description、input_schema、execute。name 是工具名,模型通过它来引用;description 是给模型的说明书,告诉它这个工具什么时候该用;input_schema 是 JSON Schema,约束参数格式;execute 是实际执行函数。harness 启动时会把所有工具扫一遍,注册成一个映射表,模型请求里的tool_use就是靠这个名字查表找到执行函数的。
常见的内置工具大致覆盖这些领域:
| 工具类别 | 典型工具 | 用途 |
|---|---|---|
| 文件操作 | Read / Write / Edit | 读取、覆盖写入、按行编辑文件 |
| 命令执行 | Bash | 在用户机器上执行终端命令 |
| 代码搜索 | Glob / Grep | 按模式找文件、按正则搜内容 |
| 网络获取 | WebFetch / WebSearch | 抓取网页、搜索信息 |
| 任务分发 | Task | 把子任务派给新的 agent 会话 |
看这张表你就明白为什么 Claude Code 在仓库类任务上比纯对话强:它不是只会聊天,而是把文件系统、命令行、网络全暴露成了模型可调用的函数。你在热词里看到的「Claude Code 如何直接执行终端命令」,答案就在 Bash 这个工具的 execute 实现里,它用子进程拉起 shell,再把 stdout、stderr 和退出码按固定结构返回给模型。理解了这张注册表,你就理解了所有终端 AI 编程助手的基本盘。
2.3 配置、鉴权与在线升级:API key 在源码里怎么流动
配置和鉴权是二次开发时绕不开的两个点。Claude Code 的配置分三层:用户级、项目级、本地覆盖。用户级配置在~/.claude/settings.json,项目级在.claude/settings.json,本地覆盖在.claude/settings.local.json。加载顺序是后者覆盖前者,这个层级关系在源码里有明确的合并逻辑,改配置不生效的坑大多出在这里——你改的是本地覆盖文件,但项目级文件里有个同名配置把它压回去了。
API key 的流动路径值得单独画一条线:优先读环境变量ANTHROPIC_API_KEY,读不到就查本机凭证文件,再不行才走登录流程。这里有个容易被忽略的细节:key 一旦进了环境变量,整个进程生命周期内所有请求都会带上它,所以你在源码里做请求日志时,千万别把 header 完整打出来,否则一次 verbose 输出就能把你的 key 泄露到终端历史里。
在线升级机制也住在源码里。CLI 启动时会有一次版本检查,发现远端版本比本地新就下载替换。这个机制对普通用户很友好,但对改源码的人是个隐患——你本地明明改好了,下次启动它自动给你换回发布版。后面我会专门讲怎么关掉它。
3. 本地跑通 Claude Code 源码:最小构建命令与三个启动参数
3.1 前置条件与两种拿源码的方式
本地跑源码之前,先把前置条件列清楚。Node.js 版本要够新,建议 18 及以上,太低会在构建阶段栽在语法解析上。另外需要本机能正常访问 npm registry,因为构建过程要拉一堆依赖包。操作系统不限,Windows 上用 Git Bash 或 WSL 都行,但后续调试体验 WSL 更顺。
拿源码有两条路,取决于你想干什么。如果只是想用最新功能,直接npm install -g @anthropic-ai/claude-code装发布版就行,不用碰源码;如果你要改代码、调试、加自定义工具,那就必须从官方仓库 clone 下来自己构建。区别在于:发布版是编译打包后的产物,代码被压缩过,改起来非常痛苦;源码仓库保留完整目录结构和可读的模块文件,改完重新构建就能生效。我一般建议后者,因为读源码的价值恰恰在「能改」,只读发布版那堆压缩代码,还不如直接看反编译结果。
3.2 从 zero 构建到可执行:clone、install、build 一步到底
拿到源码后的第一步是装依赖。这个项目依赖不少,网络状况不好的时候会等很久,属于正常现象。依赖装完再构建,构建脚本会把源码里的模块打包成可执行产物。完整的步骤如下:
git clone <官方仓库地址> claude-code-src cd claude-code-src npm install npm run build node bin/cli.js --helpgit clone把仓库拉到本地,地址填官方仓库就行,分支建议用默认主线。npm install安装全部依赖,这一步如果报错,多半是 Node 版本太低或网络问题,先解决前置条件再重试。npm run build执行构建,产物会输出到指定目录,这一步成功说明依赖树没问题。最后用node bin/cli.js --help验证构建结果——能打出帮助信息就说明整条链路通了。
这里有个重点:构建完一定要用node直接指向仓库里的入口文件来跑,而不是敲claude命令。因为你机器上可能已经装了发布版,claude命令指向的是全局安装目录,跟本地构建产物不是同一个东西。用node bin/cli.js才能确保跑的是你刚构建出来的源码版本。
3.3 用源码模式跑通一次对话:三条命令与参数逐项说明
构建跑通之后,先试一次最小对话。我习惯用--print参数做非交互测试,因为它不需要启动终端 UI,适合自动化和调试。最小命令长这样:
echo "列出当前目录下所有 js 文件" | node bin/cli.js --print --max-turns 3echo管道把任务文本塞进标准输入,CLI 读到输入后进入非交互模式,执行完直接退出,不会等你敲下一个问题。--print是核心参数,等价于命令里的-p,它的作用是关闭交互界面、把最终答案直接打到标准输出。--max-turns 3限制对话轮数,避免模型陷入无限循环——这在调试时非常有用,能防止一个失控的 agent 把你的 shell 搞乱。
调试源码时我还会加一个参数:
echo "解释一下这个仓库的目录结构" | node bin/cli.js --print --verbose --max-turns 5--verbose会把每次请求的模型名、token 数、工具调用记录打到日志里。改源码后跑这条命令,你能直接看到请求体里是否带上了你的改动。记住一个调试心法:先不加 verbose 跑通功能,再加 verbose 看细节。不然第一次就跑 verbose,输出会淹没在日志海洋里,反而看不清问题在哪。
4. 拆解核心机制:Agent 主循环、工具调用链与上下文压缩
4.1 主循环:一次对话在源码里是怎么转起来的
把 Claude Code 比作黑匣子,那主循环就是这个黑匣子里最值钱的一根轴线。整个 agent 逻辑可以压缩成一小段伪代码:
messages = build_initial_messages(task, system_prompt) for turn in range(max_turns): response = call_model(messages) messages.append(response) if response.is_done(): break tool_name = extract_tool_use(response) tool_args = extract_tool_args(response) if tool_name: result = tools_registry[tool_name].execute(tool_args) messages.append(build_tool_result(result)) else: break每次迭代做四件事:把当前所有消息发给模型,拿到回复后追加进消息数组;判断回复里有没有工具调用意图;有就查注册表执行对应工具;把执行结果以tool_result身份追加回去,开启下一轮。这个循环的退出条件有两个:模型回复里没有工具调用了,或者轮次达到max_turns上限。
这个设计里最值得学的一点是:模型自身不会执行任何操作,它只是输出「我想调用 Read 工具,参数是/etc/hosts」这样的结构化内容,真正动文件系统的是注册表里的 execute 函数。这也解释了为什么「直接执行终端命令」在源码里如此谨慎——模型只有调用权,没有执行权,执行权牢牢攥在工具模块手里。你在改源码时,只要守住这条边界,就能在能力开放和安全性之间找到平衡点。
4.2 权限模型:哪些命令能自动执行,哪些必须问
权限模型是 Claude Code 源码里改动需求最密集的地方,也是新手最容易改崩的地方。它的核心设计是分级放行:default模式每个工具执行前都要问用户;acceptEdits模式放行文件编辑类操作;bypassPermissions模式全部放行;plan模式只允许只读操作。除了模式,还有 allowlist 和 denylist 两套规则,分别用于精确放行和精确拦截。
权限判定发生在工具执行之前,也就是说即使模型已经输出了 tool_use,也要过一道闸门才能真正执行。源码里有一套独立的规则匹配逻辑:先查 denylist,命中直接拒绝;再查 allowlist,命中直接放行;都查不到就按当前权限模式的默认策略处理。这个顺序很关键,你改配置时如果发现规则不生效,八成是 denylist 里有个条目把 allowlist 覆盖了。
改权限源码时最常见的翻车是把 bypassPermissions 当成万能钥匙,全局一开,模型跑什么都自动执行。这在本地个人目录里问题不大,但放到 CI 环境里就是灾难——一条rm -rf的命令在自动放行下会直接执行。我见过不止一次这种事故,后来养成的习惯是:只对特定命令开自动放行,比如git status、npm test,用 allowlist 精确命中,而不是把整个权限系统关掉。
4.3 上下文压缩阈值:token 逼近上限前源码做了什么
跑过几次长任务的人都会发现一个现象:对话到后半段,模型好像「忘记」了最早的任务描述。这不是玄学,是上下文压缩机制在起作用。Claude Code 的会话基于 messages 数组,每轮都要把所有历史消息重新发给 API,token 消耗随轮次线性增长。当总 token 数逼近模型上下文窗口上限时,系统会触发压缩。
压缩策略在源码里是这么实现的:保留最近几轮的关键消息,把更早的历史交给模型生成一段摘要,用摘要替换掉原来的长消息。这个动作对用户不可见,所以你看到的现象是「它好像忘了」,实际是原始消息被摘要化了,指令细节在摘要过程中丢失了。源码里有个开关配置可以调整压缩行为,比如关闭自动压缩、手动触发压缩。
调试这个机制有个土办法:盯着 verbose 输出里的 token 消耗曲线。如果看到某轮之后 token 数突然掉下去一大截,就是压缩被触发了。理解了这一点,你就知道长任务应该怎么设计:把最核心的指令放在最近的几轮消息里,或者分阶段调用,别指望模型在 50 轮对话后还记得第一轮里的某个细节。我在源码里加自定义工具时,也会刻意让工具把关键状态写回消息流,这样压缩时不容易丢。
5. 从源码到可用的五个坑:构建失效、升级覆盖与第三方模型兼容
5.1 构建报错找不到本地包:workspace 依赖没装全
现象:npm install跑完没报错,npm run build却报「找不到模块 xxx」,而且报错的包名就是你 clone 下来的仓库里的其他目录。
原因:这个仓库不是单包工程,内部模块之间互相引用,依赖关系靠 workspace 管理。直接npm install只装了外部依赖,内部 workspace 包的软链接没全部建立,构建时一引用就炸。
解决:用npm ci替代npm install,或者显式安装 workspace 依赖:
npm ci npm run buildnpm ci会严格按照 lockfile 重建 node_modules,并处理好 workspace 链接,比 install 更干净。如果项目里没有 lockfile,就先提交一个或在 install 时指定--workspaces。判断是否装全有个快速方法:看 node_modules 里有没有指向仓库内部目录的软链接,没有就说明 workspace 没建立。
5.2 改了源码不生效:PATH 里跑的还是发布版
现象:你改了源码文件,重新构建也成功了,但运行功能时行为完全没变,好像改动被吞了一样。
原因:你敲的命令是claude,它指向的是 npm 全局安装的发布版路径,而你改的是本地仓库里的源码。PATH 环境变量里全局安装目录排在前面,shell 根本不会去执行你的构建产物。
解决:运行测试时统一用node bin/cli.js完整路径,不要用claude。如果一定要用claude命令名,可以用npm link把本地仓库链接到全局,但要注意 link 之后可能和已安装的发布版冲突,需要先卸载全局包。我的习惯是调试阶段老老实实用完整路径,省心。
5.3 接入 deepseek/qwen/glm 后工具调用返回格式崩掉
现象:通过 cc switch 这类切换器把 API 端点换到 deepseek、qwen 或 glm 之后,常规对话能跑,但模型一调工具就翻车:参数序列化错误、类型对不上、或者干脆返回一段文字而不是结构化 tool_use。
原因:Claude Code 源码里的工具调用链严格按照 Anthropic 消息格式解析。第三方模型的接口虽然兼容,但工具调用的 schema 细节不一定一致,比如参数嵌套层级不同、required 字段缺失,或是在同一次回复里同时给了多个 tool_use。源码解析器拿不到预期结构,就直接报错或跳过。
解决:在工具调用链的入口加一道参数校验和规整层,把第三方模型返回的 tool_use 结构转成源码期望的格式。常见的做法是写一个适配函数,专门处理参数名映射和类型转换。我的建议是先用--verbose把第三方模型实际返回的 JSON 打出来,对照源码里的解析逻辑逐字段查差异,比盲改快得多。另一个更省事的路子是:只在单轮里允许一个工具调用,从根上避开多工具并发导致的解析混乱。
5.4 自动批准模式把权限模型打穿:命令失控的教训
现象:开了bypassPermissions模式跑任务,模型连续执行了十几条命令,其中一条把目录下的构建产物全删了。
原因:自动放行模式下,模型对自己输出的每一条命令都有执行权,权限系统形同虚设。模型对工具的理解并不包含「哪些命令后果严重」的价值判断,它只按任务目标选择工具和参数。
解决:用 allowlist 精确放行,而不是全局关权限。源码里 allowlist 支持按命令前缀匹配,比如只放行git和npm test,其余一律询问。我在自动化脚本里跑长任务时,会先把任务要用的命令列一遍,逐个加进 allowlist,跑完之后立刻清空。这个习惯救过我不少次,比任何源码改动都管用。
5.5 在线升级悄悄覆盖你的本地修改
现象:昨天还在正常跑的本地分支,今天启动时提示版本更新,更新完发现你加的自定义工具代码全没了。
原因:源码里有启动时检查更新的逻辑,一旦发现远端版本号更高,就会拉取新版本并替换本地产物。如果你直接改的是构建输出目录,升级一覆盖就白改了。
解决:改源码之前先把自动更新关掉,在配置文件里禁用更新检查,同时把本地构建产物挪出默认安装目录。另一个稳健做法是:不要改构建产物,而是以「外部工具 + 注册表扩展」的方式把自定义逻辑挂进 harness,这样升级只更新核心部分,你的扩展不受影响。除非你要动的是 harness 核心逻辑,否则永远优先选扩展而不是改核心。
6. 进阶用法:把 agent 循环变成你自己的批处理引擎
6.1 在 Node 脚本里复用 harness 的两种常见姿势
当你啃完了主循环和工具注册表,下一步自然是把它集成进自己的项目。最常见的做法是从编译产物里引入 CLI 入口,把本来由命令行传入的参数改成在代码里直接构造。我一般保留一个跑批任务的脚本,把任务列表写在数组里,循环喂给 agent:
const { runAgent } = require('./build/entry.js'); const tasks = [ '检查 src 目录下所有 TODO 注释', '为每个 TODO 生成一条 git issue 文本', '输出汇总报告到 report.md' ]; for (const task of tasks) { const result = await runAgent({ prompt: task, maxTurns: 10, permissionMode: 'acceptEdits', cwd: process.cwd() }); console.log(result.finalText); }这段代码的关键在runAgent这个导出函数,它接收的配置对象和命令行参数一一对应。maxTurns限制每个任务的轮次,防止单个任务跑飞;permissionMode控制权限模式,批处理时一般用acceptEdits而不用全局放行;cwd指定工作目录,让每个任务在不同的仓库目录里执行。result.finalText拿到的是 agent 最终输出,也就是主循环退出时留下的最后一条非工具消息。
6.2 改完源码怎么验证:三个信号帮你确认改动生效
改完源码最怕的就是自我感觉良好,实际根本没生效。我有一套三步验证法,每次都跑一遍。第一步看 verbose 日志里有没有打到你的改动点,比如你改了工具注册表,日志里工具列表应该多出或少了一项;第二步是故意构造一个触发你改动逻辑的任务,确认它走的是新代码分支而不是旧逻辑;第三步连续跑三轮对话,确认上下文压缩没有把你加的字段洗掉。这套验证法帮我过滤掉了大半「改了但没生效」的无效调试。
回头看这段折腾源码的经历,最大的教训是:别一上来就想着重写核心循环,先读、再小改、最后才是重构。工具注册传参这种小改动带来的收益,往往比动 agent 循环大得多,风险却小一个量级。希望这篇文章能让你在啃 Claude Code 源码时少走几步弯路,把终端 AI 助手的控制权真真切切攥到自己手里,希望帮到你。
本文还有配套的精品资源,点击获取