前六篇文章,我们把 Claude Code 的内部机制拆得相当透彻了:启动层、查询引擎、工具系统、上下文管理、MCP 协议、多智能体协调------每一块都知道是什么、为什么、怎么工作。
但有一件事情还没做------动手跑起来。
光看懂源码,和真正能基于它做出东西,中间隔着一段实践的路。从本篇开始,我们把重心从「读懂」转向「做出来」,带大家一步一步完成从零到一个可用 Agent 的全过程。
一、两种调用方式:先把路选好
在动手之前,有一个问题值得先想清楚:做 Agent 有哪些路可以走?
基于我们对 Claude Code 源码的理解,答案是------两条路,复杂度不同,适合场景不同。
路线 A:Shell 脚本调用(claude -p)
最简单的方式。Claude Code 安装好之后,在任何脚本里直接调用 claude -p "你的任务" 就能拿到输出。适合快速验证想法、集成到 CI/CD 流水线、写不超过几十行的自动化脚本。
路线 B:Node.js/Bun SDK(@anthropic-ai/claude-code)
稍微复杂一些,但能力强大得多。可以拿到完整的流式消息流、自定义工具、控制权限模式、处理多轮对话......回顾第二篇讲到的 QueryEngine,这条路就是直接使用它。适合要做产品级别的 Agent、需要接入自有系统的场景。
本篇两条路都带大家走一遍,让大家根据实际需要选择。
二、路线 A:三行代码跑起来
前置准备:确保已经安装了 Claude Code 并完成了登录。
bash
# 安装(如果还没有)
npm install -g @anthropic-ai/claude-code
# 验证安装
claude --version
最简单的 Agent 调用:
bash
claude -p "列出当前目录下所有 TypeScript 文件,并统计每个文件的行数"
可以看到 Claude 会自动调用 Bash 工具执行 find 和 wc -l,完成后打印出结果。整个过程不需要一行业务代码。
在 Shell 脚本里使用:
bash
#!/usr/bin/env bash
# 一个简单的代码分析脚本:analyze.sh
PROJECT_DIR=$1
if [ -z "$PROJECT_DIR" ]; then
echo "用法: ./analyze.sh <项目目录>"
exit 1
fi
echo "正在分析 $PROJECT_DIR ..."
claude -p "
请分析 $PROJECT_DIR 目录下的代码,完成以下任务:
1. 统计各类型文件的数量(.ts、.tsx、.js 等)
2. 找出代码量最大的 5 个文件
3. 识别项目使用的主要依赖(看 package.json)
4. 给出一段 3-5 句话的项目概述
用中文输出结果。
" --cwd "$PROJECT_DIR"
运行方式:
bash
chmod +x analyze.sh
./analyze.sh ~/my-project
这里需要注意的是,-p 模式下 Claude 默认在当前目录下工作,用 --cwd 参数可以指定工作目录。
在 CI/CD 里使用:
yaml
# .github/workflows/ai-review.yml
name: AI Code Review
on: [pull_request]
jobs:
ai-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: 运行 AI 代码审查
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
# 拿到本次 PR 的变更文件列表
CHANGED_FILES=$(git diff --name-only origin/main...HEAD)
claude -p "
请对以下变更文件做简短的代码审查,重点关注:
- 潜在的 Bug
- 安全风险
- 明显的性能问题
变更文件:$CHANGED_FILES
" --output-format=json > review.json
# 把结果发到 PR 评论(可用 gh 命令)
cat review.json
--output-format=json 参数让输出变成结构化的 JSON,方便后续处理。
三、路线 B:用 SDK 构建可控的 Agent
当需要更细粒度的控制时,就要用 SDK 了。
安装依赖:
bash
# 在你的 Node.js 或 Bun 项目里
npm install @anthropic-ai/claude-code
# 或者
bun add @anthropic-ai/claude-code
最简单的 SDK 调用:
typescript
// agent.ts
import { query } from '@anthropic-ai/claude-code'
async function runAgent() {
const response = query({
prompt: '分析当前目录的 package.json,告诉我这个项目用了哪些主要框架',
options: {
cwd: process.cwd(),
},
})
// for await 消费流式消息
for await (const message of response) {
// 打印 Claude 的文字回复(流式,逐块输出)
if (message.type === 'assistant') {
const content = message.message?.content
if (Array.isArray(content)) {
for (const block of content) {
if (block.type === 'text') {
process.stdout.write(block.text)
}
}
}
}
// 任务结束
if (message.type === 'result') {
console.log('\n\n--- 任务完成 ---')
console.log(`耗时: ${message.duration_ms}ms`)
console.log(`花费: $${message.total_cost_usd?.toFixed(4)}`)
break
}
}
}
runAgent()
运行:
bash
bun run agent.ts
# 或
npx tsx agent.ts
理解消息流:
回顾第二篇讲的 AsyncGenerator,query() 返回的就是一个消息流。消息有不同的 type,大家需要知道几个最关键的:
message.type |
含义 | 什么时候出现 |
|---|---|---|
assistant |
Claude 的文字或工具调用 | 每次 Claude 有输出 |
user |
工具执行结果(tool_result) | Claude 调用工具后 |
result |
整轮任务结束的汇总 | 最后一条消息 |
system |
系统级事件(如压缩、错误重试) | 按需出现 |
处理消息流的完整代码模板:
typescript
import { query } from '@anthropic-ai/claude-code'
async function runAgent(prompt: string) {
const response = query({
prompt,
options: { cwd: process.cwd() },
})
for await (const message of response) {
switch (message.type) {
case 'assistant': {
// 打印文字内容
const blocks = message.message?.content ?? []
for (const block of blocks) {
if (block.type === 'text' && block.text) {
process.stdout.write(block.text)
}
// tool_use 块:Claude 决定调用某个工具
if (block.type === 'tool_use') {
console.log(`\n[工具调用] ${block.name}(${JSON.stringify(block.input)})`)
}
}
break
}
case 'user': {
// 工具执行结果
const results = message.message?.content ?? []
for (const block of results) {
if (block.type === 'tool_result') {
console.log(`[工具结果] ${String(block.content).slice(0, 100)}...`)
}
}
break
}
case 'result': {
// 任务结束
if (message.subtype === 'success') {
console.log('\n✓ 任务成功完成')
} else {
console.log(`\n✗ 任务失败: ${message.subtype}`)
if (message.errors) {
console.log('错误信息:', message.errors)
}
}
return message // 返回最终结果
}
case 'system': {
if (message.subtype === 'api_retry') {
console.log(`[重试] 第 ${message.attempt} 次重试...`)
}
break
}
}
}
}
// 使用
await runAgent('列出所有 .ts 文件并找出最长的函数')
四、第一个有意义的 Agent:代码变更摘要器
理论讲完了,我们来做一个真实有用的东西:一个自动总结 git diff 的 Agent。
在代码 review 场景里,最耗时的部分之一是「理解这次提交改了什么」。让 Agent 做这件事,是一个非常合适的起点。
typescript
// summarize-diff.ts
import { query } from '@anthropic-ai/claude-code'
import { execSync } from 'child_process'
async function summarizeGitDiff(baseBranch = 'main') {
// 先拿到 diff 内容
let diff: string
try {
diff = execSync(`git diff ${baseBranch}...HEAD`, {
encoding: 'utf-8',
maxBuffer: 10 * 1024 * 1024, // 10MB 上限
})
} catch (e) {
console.error('无法获取 git diff,请确保在 git 仓库目录下运行')
process.exit(1)
}
if (!diff.trim()) {
console.log('和主分支没有差异')
return
}
// 截断过长的 diff,避免超出 context
const MAX_DIFF_CHARS = 50_000
const truncated = diff.length > MAX_DIFF_CHARS
const diffToAnalyze = truncated
? diff.slice(0, MAX_DIFF_CHARS) + '\n\n[... diff 内容过长,已截断]'
: diff
console.log(`分析 ${diff.length} 字符的 diff,请稍候...\n`)
const prompt = `
请分析以下 git diff,用中文输出一份结构化的变更摘要:
要求:
1. 用一句话概括本次变更的核心目的
2. 列出主要变更点(3-7 条,按重要程度排序)
3. 标注需要重点关注的风险点(如果有)
4. 格式要简洁,方便直接粘贴到 PR 描述里
diff 内容:
\`\`\`
${diffToAnalyze}
\`\`\`
`
const response = query({
prompt,
options: {
cwd: process.cwd(),
// 这个任务只需要分析文本,不需要工具
allowedTools: [],
},
})
process.stdout.write('📋 变更摘要:\n\n')
for await (const message of response) {
if (message.type === 'assistant') {
const blocks = message.message?.content ?? []
for (const block of blocks) {
if (block.type === 'text') {
process.stdout.write(block.text)
}
}
}
if (message.type === 'result') {
console.log(`\n\n[耗时 ${message.duration_ms}ms,花费 $${message.total_cost_usd?.toFixed(5)}]`)
break
}
}
}
// 从命令行参数读取 base branch,默认 main
const baseBranch = process.argv[2] ?? 'main'
await summarizeGitDiff(baseBranch)
运行效果:
bash
bun run summarize-diff.ts
# 或者指定 base branch
bun run summarize-diff.ts develop
输出大致如下:
分析 8432 字符的 diff,请稍候...
📋 变更摘要:
**核心目的**:重构工具权限检查模块,将散落在各工具中的权限逻辑收敛到统一的 PermissionManager 类。
**主要变更**:
- 新增 `PermissionManager` 类(`src/utils/permissions/manager.ts`),统一管理工具权限规则
- `BashTool` 移除内联权限逻辑,改为调用 `PermissionManager.canUse()`
- `FileEditTool` 同步更新,使用新的权限接口
- 更新 `QueryEngine.ts` 中的 `wrappedCanUseTool`,适配新接口
- 补充了 `PermissionManager` 的单元测试(`tests/permissions/manager.test.ts`)
**风险点**:
- 旧的 `bashToolHasPermission` 函数仍保留(backward compat shim),可以在下个版本删除
- 需要验证 `bypassPermissions` 模式下的行为是否与之前一致
[耗时 4821ms,花费 $0.00312]
这个 Agent 的实用价值很高------每次提交 PR 之前跑一次,自动生成 PR 描述的第一稿,比手写快多了。
五、控制权限模式
大家可能注意到,默认情况下 Agent 运行时,Claude 调用工具仍然会弹出确认框。在自动化场景里,这显然不合适。
可以通过 permissionMode 选项来控制:
typescript
const response = query({
prompt: '...',
options: {
cwd: process.cwd(),
// 完全绕过权限确认(适合受信任的自动化脚本)
permissionMode: 'bypassPermissions',
},
})
这里需要特别说明------bypassPermissions 意味着 Claude 可以不经确认地读写任何文件、执行任何命令。在本地开发脚本里用是可以的,但在接受外部输入(比如读取用户提交的代码来处理)的场景里,一定要结合其他安全措施,不要无条件开启。
三种权限模式的选择原则:
default:交互式场景,危险操作需要确认acceptEdits:半自动化场景,接受文件改动但 Bash 仍需确认bypassPermissions:完全自动化的受信任脚本
六、多轮对话
query() 的单次调用相当于「一个用户消息 → Claude 执行到结束」。如果需要多轮对话,可以用 --resume 恢复会话,或者在 SDK 里使用 sessionId:
typescript
import { query } from '@anthropic-ai/claude-code'
// 第一轮
let sessionId: string | undefined
const response1 = query({
prompt: '帮我找出项目里所有的 TODO 注释',
options: { cwd: process.cwd() },
})
for await (const message of response1) {
if (message.type === 'result') {
sessionId = message.session_id // 保存 session id
console.log('找到的 TODO:', message.result)
break
}
}
// 第二轮:在同一个 session 里继续
if (sessionId) {
const response2 = query({
prompt: '针对刚才找到的这些 TODO,哪些是高优先级的?',
options: {
cwd: process.cwd(),
resume: sessionId, // 恢复上一轮的上下文
},
})
for await (const message of response2) {
if (message.type === 'assistant') {
const blocks = message.message?.content ?? []
for (const block of blocks) {
if (block.type === 'text') process.stdout.write(block.text)
}
}
if (message.type === 'result') break
}
}
这就像两位同事之间的接力对话------第二位同事接手时,完全知道第一轮聊了什么,不需要从头解释背景。
学习完本篇,大家应该能做到:
- 用
claude -p在脚本和 CI/CD 里快速集成 AI 能力 - 用 SDK 的
query()函数获取完整的流式消息流 - 处理
assistant、user、result等不同类型的消息 - 控制权限模式,让 Agent 在自动化场景里无需交互确认
- 用
session_id实现多轮对话
如果第一次写这些代码遇到报错,先不要纠结。先确认安装正确、API key 设置正确、在 git 仓库目录下运行,大部分问题都出在环境配置上,和代码逻辑无关。
接下来,进入第八篇------自定义工具,给 Agent 加上只有你才有的专属能力。