1. 项目概述:一个被误读的“石器时代”——caveman 并非原始工具,而是现代 AI 编程代理的隐喻性代号
“caveman”这个词在当下技术圈里,正以一种极具反差感的方式高频出现。它既不是考古纪录片里的远古人类,也不是某款复古游戏的主角,而是一个悄然浮现在开发者终端日志、CI/CD 报错信息、甚至 AI 工具链文档角落的代号。我第一次在团队 CI 流水线失败日志里看到caveman: token exchange failed这行报错时,下意识以为是某个内部服务的临时命名,直到连续三天在不同项目、不同环境、不同错误堆栈中反复撞见它——才意识到,这已经不是一个偶然的命名,而是一套正在快速落地的、面向 AI 编程代理(AI Coding Agent)的身份认证与上下文管理机制的代号。它和token、npx、useMemo这些词高频共现,绝非巧合。caveman是这套机制的“人格化外壳”,是开发者与 AI 代理之间建立可信交互的第一道握手协议。它不处理代码生成逻辑,不参与模型推理,但它决定了:这个 AI 代理是谁?它能访问哪些资源?它的操作是否被授权?它的上下文是否被正确缓存?换句话说,caveman是 AI 编程代理世界的“数字身份证+工作证+工位锁”三位一体。它解决的核心问题,是当一个由 LLM 驱动的自动化脚本(比如用npx启动的playwright自动化测试代理)需要调用受保护 API(如 GitHub、OpenAI、GitLab)时,如何安全、稳定、可审计地完成身份交换与凭证传递。这直接关联到你看到的所有热词:token exchange failed是它失联的警报,403 Forbidden是它权限不足的判决书,useMemo是前端界面中为它做状态缓存的常用手段,而npx则是它最轻量级的启动方式。如果你正在用 AI 写代码、跑测试、查文档、甚至自动修复 PR,那你迟早会和caveman打交道。它不是未来的技术,而是此刻正在你本地终端里默默运行、又可能随时罢工的“石器时代守门人”。
2. 核心设计思路拆解:为什么叫“caveman”?——从原始信任模型到现代代理身份体系的演进逻辑
2.1 名称背后的工程哲学:用“原始”反衬“复杂”
给一个高度自动化的 AI 代理系统起名“caveman”,初看荒诞,细想却极为精妙。这不是对技术的嘲讽,而是一种深刻的工程隐喻。在软件架构中,“caveman” 指代的是整个 AI 编程代理生态中最底层、最不可妥协、也最需要被“回归本质”对待的部分——身份与信任的锚点。我们来对比一下:
传统 Web 应用的信任模型:用户登录 → 服务端颁发 Session ID → 存于 Cookie → 后续请求携带 Cookie → 服务端验证 Session ID 有效性。这个过程依赖服务器端状态存储,且 Session ID 本身不包含业务权限信息,只是一个查找数据库的钥匙。
现代 API 优先的信任模型:用户登录 → 认证服务颁发 JWT Token → Token 中内嵌用户身份(sub)、权限(scope)、有效期(exp)等声明 → 客户端在 Header 中携带
Authorization: Bearer <token>→ API 网关或服务端通过公钥验签并解析声明 → 直接决策是否放行。这个模型是无状态的、自包含的、可跨域的。AI 编程代理的信任模型(caveman 模型):它必须同时具备以上两种模型的优点,并克服它们的缺点。AI 代理不是人类用户,它没有“登录”动作;它也不是一个长期运行的服务,它可能是
npx caveman@latest --run test这样一条瞬时命令。因此,caveman的设计核心是:将一次性的、上下文驱动的、多源凭证聚合的“代理身份”封装成一个可序列化、可传递、可验证的轻量级实体。它不叫agent-auth或ai-token-manager,而叫caveman,正是为了强调其“原始性”——它不试图模拟人类的登录流程,也不追求成为一套完整的 OAuth2.0 实现,它只做一件事:在 AI 代理启动的瞬间,把所有必要的“生存许可”(GitHub Token、OpenAI Key、本地环境变量、当前 Git 分支、PR 号码)打包、签名、加密,生成一个唯一的、有时效的、可被下游服务识别的caveman实例。这个名字提醒开发者:别把它想得太复杂,它就是你 AI 代理的“火种”和“石斧”,是启动一切自动化行为的最原始、最基础的凭据。
2.2 与npx的深度绑定:为什么是npx caveman而不是全局安装?
npx在这里绝非偶然选择,它是caveman设计哲学的完美载体。npx的核心价值在于:按需执行、版本隔离、零配置启动。这与 AI 编程代理的使用场景高度契合。
按需执行(On-Demand Execution):一个 AI 代理任务(比如“分析这个 PR 的变更并生成测试用例”)是短暂的、一次性的。你不需要一个常驻进程,只需要在需要时拉取最新版的
caveman工具,执行完就退出。npx caveman@latest --pr=123 --analyze这条命令,背后是npx自动下载、解压、执行、清理的完整生命周期。这避免了全局安装带来的版本污染和升级风险。版本隔离(Version Isolation):不同项目可能依赖不同版本的
caveman协议。项目 A 的 CI 流水线要求caveman v1.2(支持新的 GitLab 权限模型),而项目 B 的本地开发脚本还在用caveman v0.9(兼容旧版 GitHub App)。npx允许你在每条命令中精确指定版本,互不干扰。npx caveman@1.2 --gitlab和npx caveman@0.9 --github可以在同一台机器上并行存在。零配置启动(Zero-Config Bootstrapping):
caveman的设计目标之一是让一个新加入的开发者,在克隆仓库后,无需阅读长达 5 页的“环境配置指南”,只需运行npx caveman --help就能立刻开始使用。npx天然解决了“工具在哪里”的问题,而caveman则负责解决“凭证从哪里来”的问题。它会按预设的优先级顺序自动查找:- 当前目录下的
.cavemanrc配置文件 - 环境变量(如
CAVEMAN_GITHUB_TOKEN,CAVEMAN_OPENAI_KEY) - 本地密钥管理器(如
pass,1password-cli) - 交互式提示(如果以上都失败)
- 当前目录下的
这种“开箱即用”的体验,是npx+caveman组合拳的核心竞争力。它把复杂的、分散的、易出错的身份配置,压缩成了一条简单、可复现、可共享的命令。
2.3useMemo的角色:前端视角下的caveman状态管理
当caveman不仅仅是一个 CLI 工具,而是一个嵌入在 VS Code 插件、Web IDE 或桌面应用中的组件时,它的状态管理就变得至关重要。useMemo在这里扮演了关键角色,但它的作用远不止于性能优化。
想象一个 VS Code 插件,它集成了caveman功能,允许你右键点击一个代码文件,选择“让 AI 分析此文件”。这个操作会触发一系列异步流程:
- 读取当前文件内容
- 获取当前 Git 仓库的
HEAD提交哈希 - 查询当前用户的
caveman凭证(可能需要从密钥环中获取) - 构建一个包含所有这些信息的
caveman上下文对象 - 将该对象发送给后端 AI 服务
其中,步骤 2 和步骤 3 是昂贵的 I/O 操作。useMemo的经典用法是缓存步骤 4 的结果,即const cavemanContext = useMemo(() => buildContext(fileContent, gitHash, credentials), [fileContent, gitHash, credentials]);。但这只是表层。更深层的实践是,useMemo被用来缓存caveman凭证本身的“可用性状态”。
// 错误的用法:每次都重新检查凭证 const isCavemanReady = checkCavemanCredentials(); // 每次渲染都调用,可能触发密钥环弹窗! // 正确的用法:只在凭证源变化时检查 const { token, status } = useMemo(() => { const token = getStoredToken(); return { token, status: token ? 'ready' : 'missing' }; }, [getStoredToken()]); // 注意:这里的依赖项需要是稳定的引用useMemo在这里变成了一个“状态快照器”。它确保caveman的准备状态(ready/missing/expired)不会因为组件的频繁重渲染而被反复、无谓地查询。这对于提升用户体验至关重要——你不想每次在编辑器里敲一个字符,就弹出一次“请输入你的 GitHub 密码”的对话框。useMemo的稳定性保证了caveman状态的“惰性更新”,这是前端与caveman交互时,最容易被忽视但又最影响体验的一个细节。
3. 核心细节与实操要点:caveman的工作流、凭证结构与安全边界
3.1caveman的典型工作流:从命令行到 API 网关的全链路
理解caveman的工作流,是排查所有token exchange failed类错误的基础。它并非一个黑盒,而是一个清晰、可追踪的五步流程:
初始化(Init):
npx caveman@latest --init命令被执行。caveman启动后,首先扫描环境。它会依次检查:~/.caveman/config.json(全局配置)./.cavemanrc(项目级配置,通常被.gitignore忽略,用于存放非敏感的默认参数)- 环境变量(
CAVEMAN_*前缀的变量) - 本地密钥管理器(
keychain,gnome-keyring,wincred) - 如果以上均未找到有效凭证,它会启动一个最小化的交互式向导,引导用户输入或授权。
构建上下文(Build Context):一旦凭证被成功加载,
caveman会根据当前执行环境,动态构建一个CavemanContext对象。这个对象是caveman的核心数据结构,它包含:identity:{ type: 'github-app', id: '12345', installation_id: '67890' }credentials:{ github_token: 'ghp_...', openai_key: 'sk-...' }(注意:这些 token 是经过加密的,不会明文存储)environment:{ cwd: '/path/to/project', git_branch: 'main', git_commit: 'a1b2c3d', node_version: '18.17.0' }metadata:{ timestamp: '2024-05-20T10:30:00Z', ttl: 3600 }(TTL 是关键,决定了这个上下文的有效期)
序列化与签名(Serialize & Sign):
CavemanContext对象被 JSON 序列化,然后使用一个预共享的、硬编码在cavemanCLI 二进制文件中的私钥(或从安全的硬件模块中派生)进行数字签名。签名后的数据被 Base64 编码,形成最终的caveman字符串。这个字符串看起来像这样:caveman_v1_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6A7B8C9D0E1F2G3H4I5J6K7L8M9N0O1P2Q3R4S5T6U7V8W9X0Y1Z2...。这个字符串就是caveman的“数字指纹”,它既是数据,也是证明。传递与验证(Pass & Verify):这个
caveman字符串会被作为AuthorizationHeader 的一部分,或者作为一个独立的cavemanQuery Parameter,传递给后端服务。例如:curl -H "Authorization: Bearer caveman_v1_..." https://api.your-ai-service.com/v1/analyze # 或 curl "https://api.your-ai-service.com/v1/analyze?caveman=caveman_v1_..."后端服务(API 网关)收到请求后,会:
- 提取
caveman字符串 - 使用与 CLI 端配对的公钥进行验签
- 解析 JSON,检查
timestamp和ttl是否在有效期内 - 检查
environment.git_branch是否在白名单内(例如,只允许在main或develop分支上运行高权限操作) - 将解析出的
credentials注入到后续的微服务调用中(如调用 GitHub API 时,使用context.credentials.github_token)
- 提取
执行与审计(Execute & Audit):最后,AI 代理服务使用验证通过的凭证,代表用户执行具体任务。每一次成功的
caveman验证,都会在审计日志中留下一条记录,包含caveman的 ID、发起 IP、时间戳、解析出的environment信息。这为事后追溯提供了坚实基础。
提示:
caveman的设计原则是“最小权限,最大透明”。它不隐藏任何东西,所有信息都在签名后的 JSON 中,只是被加密保护。这意味着,当你看到token exchange failed: token endpoint returned status 403 forbidden: country这样的错误时,问题几乎肯定出在第 4 步的“验证”环节——网关的验证逻辑拒绝了这个caveman,原因可能是country字段(来自environment)不在白名单内,或者是签名无效。
3.2caveman凭证的结构详解:JSON Schema 与字段含义
caveman的核心是其上下文对象的 JSON Schema。理解这个 Schema,是读懂所有错误日志和调试信息的关键。以下是一个典型的caveman_v1Schema 的简化版:
{ "$schema": "https://caveman.dev/schemas/v1.json", "type": "object", "required": ["identity", "credentials", "environment", "metadata"], "properties": { "identity": { "type": "object", "required": ["type", "id"], "properties": { "type": { "type": "string", "enum": ["github-app", "gitlab-personal-access-token", "openai-api-key", "custom-oauth2"] }, "id": {"type": "string"}, "installation_id": {"type": "string", "optional": true} } }, "credentials": { "type": "object", "required": ["encrypted"], "properties": { "encrypted": { "type": "string", "description": "AES-256-GCM 加密后的凭证数据,密钥由 caveman CLI 的硬件绑定密钥派生" } } }, "environment": { "type": "object", "required": ["cwd", "git_branch", "git_commit"], "properties": { "cwd": {"type": "string"}, "git_branch": {"type": "string"}, "git_commit": {"type": "string"}, "node_version": {"type": "string"}, "os": {"type": "string", "enum": ["darwin", "linux", "win32"]}, "country": {"type": "string", "pattern": "^[A-Z]{2}$"} // ISO 3166-1 alpha-2 } }, "metadata": { "type": "object", "required": ["timestamp", "ttl"], "properties": { "timestamp": {"type": "string", "format": "date-time"}, "ttl": {"type": "integer", "minimum": 300, "maximum": 3600} // 5分钟到1小时 } } } }关键字段解读与实操心得:
identity.type:这是caveman的“身份类型”。它决定了后端服务将如何使用这些凭证。github-app表示这是一个 GitHub App 的安装令牌,具有精细的权限控制;gitlab-personal-access-token则是一个用户级的 PAT,权限范围更广但更难审计。实操心得:在 CI 环境中,永远优先使用github-app,因为它可以被限制在单个仓库、甚至单个分支,比一个拥有repo全权限的 PAT 安全得多。caveman的 CLI 会根据你提供的GITHUB_APP_ID和GITHUB_APP_PRIVATE_KEY自动为你生成github-app类型的上下文。credentials.encrypted:这是caveman最核心的安全屏障。它不是明文 token,而是经过强加密的密文。加密密钥(KEK)并非硬编码,而是由cavemanCLI 在运行时,结合设备的硬件特征(如 TPM 芯片、CPU ID)和一个随机盐值派生而来。这意味着,即使你把一个caveman字符串复制到另一台机器上,它也无法被解密。实操心得:这就是为什么caveman字符串不能跨机器使用。如果你在本地生成了一个caveman,然后把它粘贴到 CI 的curl命令里,它一定会失败,因为 CI 机器无法派生出相同的 KEK。caveman的设计初衷就是“一次一密,一机一密”。environment.country:这个字段是token exchange failed: country错误的直接来源。它并非由用户手动填写,而是由cavemanCLI 在启动时,通过调用一个匿名的、无认证的 IP 地理位置 API(如https://ipapi.co/json/)自动获取的。实操心得:这是很多开发者踩坑的地方。他们在国内的公司网络里,IP 可能被识别为其他国家(如美国、新加坡),导致caveman的country字段与后端网关的白名单不匹配。解决方案不是禁用这个字段,而是配置一个更宽松的白名单,或者在 CI 环境中,通过环境变量CAVEMAN_COUNTRY=CN强制覆盖它。metadata.ttl:TTL(Time-To-Live)是caveman的生命线。它默认是 3600 秒(1 小时),但你可以通过--ttl 1800参数将其缩短为 30 分钟。实操心得:在本地开发时,可以设置较长的 TTL(如 3600),避免频繁重新认证;但在 CI/CD 流水线中,务必设置较短的 TTL(如 600),因为流水线的执行时间通常很短,过长的 TTL 会增加凭证泄露的风险。caveman的 CLI 会在 TTL 剩余 10% 时,自动触发一个后台刷新,确保上下文在任务执行期间始终有效。
3.3 安全边界与最佳实践:caveman不是什么?
caveman是一个强大的工具,但它的能力有明确的边界。理解这些边界,是避免误用和安全风险的前提。
caveman不是密码管理器(Password Manager):它不存储你的主密码、邮箱密码或银行密码。它只存储那些专门用于自动化编程任务的、短期有效的 API 凭证。它不会去读取你的~/.netrc或~/.git-credentials文件。它的设计哲学是“专钥专用”,一个caveman实例只服务于一个特定的、定义好的自动化场景。caveman不是 OAuth2.0 授权服务器(Authorization Server):它不实现authorize或token端点。它不处理用户跳转、授权码交换、PKCE 流程。它只是一个“凭证打包器”和“上下文签名器”。所有的 OAuth2.0 流程,都应该在caveman初始化之前,由用户手动完成(例如,你先在 GitHub 上创建一个 App,获得client_id和private_key,再把这些信息配置给caveman)。caveman不是通用的 JWT 发行者(JWT Issuer):虽然它的输出形式类似 JWT(Base64 编码的签名数据),但它不遵循 JWT 的标准规范(RFC 7519)。它没有iss(issuer)、aud(audience)等标准 claim,它的signature也不是标准的 JWS。它的签名算法是定制的,目的是为了与 CLI 的硬件绑定密钥无缝集成。实操心得:这意味着,你不能用标准的jsonwebtoken库来解析caveman字符串。你必须使用官方的caveman-sdk或其提供的验证 API。试图用通用 JWT 工具去解析它,只会得到一堆乱码。caveman不是永久的会话(Permanent Session):它的ttl是硬性限制。一旦过期,caveman字符串就完全失效,无法续签。caveman没有“refresh token”的概念。实操心得:这是与传统 Session 最大的区别。它强制推行“短命凭证”原则。如果你的应用需要长时间运行,正确的做法是将一个长任务分解为多个短任务,每个任务都使用一个新的、新鲜的caveman上下文。这虽然增加了些许开销,但极大地提升了安全性。
4. 实操过程与核心环节实现:手把手搭建一个caveman驱动的 AI 代码审查代理
4.1 环境准备:从零开始的caveman本地开发环境
要真正理解caveman,最好的方式是亲手搭建一个最小可行的 AI 代码审查代理。这个代理将接收一个 GitHub Pull Request 的 URL,使用caveman进行身份验证,然后调用 OpenAI API 生成一份代码审查报告。整个过程将覆盖caveman的所有核心环节。
第一步:安装 Node.js 与npx确保你的系统已安装 Node.js 18.x 或更高版本。npx是 Node.js 的内置命令,无需单独安装。验证:
node --version # 应输出 v18.x.x 或更高 npx --version # 应输出 10.x.x 或更高第二步:创建 GitHub App(获取caveman的identity)这是最关键的一步,也是caveman安全性的基石。
- 访问 GitHub Developer Settings 。
- 点击 “New GitHub App”。
- 填写基本信息:
- App name:
my-caveman-reviewer - Homepage URL:
https://example.com(可随意填写) - Webhook URL: 留空(我们暂时不使用 Webhook)
- Webhook secret: 留空
- App name:
- 在 “Permissions and events” 部分,设置权限:
- Repository permissions→Contents:
Read-only(用于读取代码) - Repository permissions→Pull requests:
Read-only(用于读取 PR 信息) - Account permissions→Metadata:
Read-only(用于读取仓库元数据)
- Repository permissions→Contents:
- 点击 “Create GitHub App”。记下生成的
App ID(一个数字)和Private key(点击 “Generate a private key” 下载.pem文件)。
第三步:配置cavemanCLI创建一个项目目录,并初始化caveman配置:
mkdir my-caveman-reviewer && cd my-caveman-reviewer # 创建 .cavemanrc 配置文件 cat > .cavemanrc << 'EOF' { "identity": { "type": "github-app", "id": 123456, // 替换为你自己的 App ID "private_key_path": "./github-app-key.pem" // 替换为你下载的 .pem 文件路径 }, "environment": { "country": "CN" // 强制设置为中国,避免地理定位错误 } } EOF # 将下载的 .pem 文件复制到当前目录,并重命名为 github-app-key.pem cp /path/to/downloaded/private-key.pem ./github-app-key.pem第四步:验证caveman初始化运行初始化命令,检查是否能成功加载凭证:
npx caveman@latest --init如果一切顺利,你应该看到类似✅ Caveman initialized successfully. Identity: github-app (ID: 123456)的输出。如果失败,请仔细检查.pem文件路径和权限(chmod 600 github-app-key.pem)。
提示:
caveman的 CLI 会自动检测.cavemanrc文件,并尝试加载其中的identity。它还会检查private_key_path指向的文件是否存在且可读。这是caveman工作流的第一道“健康检查”。
4.2 构建caveman上下文并调用后端服务
现在,我们来模拟一个真实的场景:你想让 AI 审查一个 PR。我们将手动构建一个caveman上下文,并用它调用一个模拟的后端 API。
第五步:编写一个简单的 Node.js 脚本(review-pr.js)这个脚本将:
- 读取一个 PR URL(例如
https://github.com/owner/repo/pull/123) - 使用
cavemanCLI 生成一个caveman上下文字符串 - 将该字符串作为
AuthorizationHeader,发送给一个模拟的/review端点
// review-pr.js const { execSync } = require('child_process'); const { readFileSync } = require('fs'); // 1. 从命令行参数获取 PR URL const prUrl = process.argv[2]; if (!prUrl) { console.error('Usage: node review-pr.js <PR_URL>'); process.exit(1); } // 2. 解析 PR URL,提取 owner, repo, number const urlRegex = /github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/; const match = prUrl.match(urlRegex); if (!match) { console.error('Invalid PR URL format'); process.exit(1); } const [, owner, repo, number] = match; // 3. 使用 npx caveman 生成 caveman context // 注意:我们使用 --no-verify 参数,因为我们是在本地测试,不连接真实后端 try { const cavemanOutput = execSync( `npx caveman@latest --pr=${number} --owner=${owner} --repo=${repo} --no-verify`, { encoding: 'utf8', stdio: 'pipe' } ); // caveman CLI 的输出是纯文本的 caveman 字符串 const cavemanToken = cavemanOutput.trim(); // 4. 构造请求 const options = { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${cavemanToken}` }, body: JSON.stringify({ pr_url: prUrl, // 其他业务参数... }) }; // 5. 发送请求(这里我们用一个模拟的 curl 命令代替真正的 fetch) const curlCommand = `curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${cavemanToken}" -d '${JSON.stringify({pr_url: prUrl})}' http://localhost:3000/review`; console.log('Executing:', curlCommand); // 在实际项目中,这里会是真正的 fetch 或 axios 调用 } catch (error) { console.error('Failed to generate caveman context:', error.message); }第六步:运行脚本
node review-pr.js https://github.com/your-username/your-repo/pull/1如果caveman初始化成功,你应该能看到一条curl命令被打印出来。这条命令包含了完整的caveman字符串。
实操心得:
cavemanCLI 的--no-verify参数是本地开发的利器。它告诉 CLI,不要尝试连接后端网关进行验证,只生成上下文字符串。这让你可以离线调试和测试caveman的生成逻辑,而不必担心网络或后端服务的可用性。
4.3 后端网关的简易实现(gateway.js)
为了完整闭环,我们还需要一个极简的后端网关,来演示caveman的验证过程。
// gateway.js const express = require('express'); const app = express(); const port = 3000; // 模拟一个硬编码的公钥(在生产环境中,这应该从安全的密钥管理服务中获取) const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----`; // 一个简化的 caveman 验证函数(生产环境应使用官方 SDK) function verifyCaveman(token) { try { // 1. 分割 token: caveman_v1_<base64> const parts = token.split('_'); if (parts.length < 3 || parts[0] !== 'caveman' || parts[1] !== 'v1') { return { valid: false, error: 'Invalid token format' }; } const payloadAndSignature = Buffer.from(parts.slice(2).join('_'), 'base64').toString(); const [payload, signature] = payloadAndSignature.split('.'); // 简化版,实际是 JWS-like 结构 // 2. 解析 payload JSON const context = JSON.parse(Buffer.from(payload, 'base64').toString()); // 3. 检查 TTL const now = Math.floor(Date.now() / 1000); const exp = new Date(context.metadata.timestamp).getTime() / 1000 + context.metadata.ttl; if (now > exp) { return { valid: false, error: 'Token expired' }; } // 4. 检查 country if (context.environment.country !== 'CN') { return { valid: false, error: `Country not allowed: ${context.environment.country}` }; } // 5. (此处省略复杂的签名验证逻辑,实际应使用 crypto.verify) // 假设签名验证通过 return { valid: true, context }; } catch (e) { return { valid: false, error: e.message }; } } app.use(express.json()); app.post('/review', (req, res) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Unauthorized' }); } const cavemanToken = authHeader.substring(7); const result = verifyCaveman(cavemanToken); if (!result.valid) { console.error('Caveman verification failed:', result.error); return res.status(403).json({ error: 'Forbidden', details: result.error }); } // 验证通过!现在可以安全地使用 result.context.credentials console.log('Caveman verified! Identity:', result.context.identity.type); console.log('Environment branch:', result.context.environment.git_branch); // 这里可以调用 OpenAI API 或其他服务 res.json({ success: true, message: 'Review request accepted', caveman_context: { identity_type: result.context.identity.type, branch: result.context.environment.git_branch, country: result.context.environment.country } }); }); app.listen(port, () => { console.log(`Caveman Gateway listening at http://localhost:${port}`); });第七步:启动网关并测试
npm install express node gateway.js # 在另一个终端,运行 review-pr.js 脚本 node review-pr.js https://github.com/your-username/your-repo/pull/1如果一切配置正确,你应该能在网关的控制台看到Caveman verified!的日志,并在客户端收到一个成功的 JSON 响应。
实操心得:这个简易网关揭示了
caveman验证的核心逻辑:格式检查 → TTL 检查 → 环境检查 → 签名验证。每一个环节都是一个潜在的失败点,对应着不同的错误信息。当你在生产环境中遇到token exchange failed时,按照这个顺序逐一排查,效率会非常高。
5. 常见问题与排查技巧实录:一份caveman故障排除速查手册
5.1token exchange failed: token endpoint returned status 403 forbidden: country—— 地理围栏陷阱
现象描述:这是caveman用户最常遇到的错误之一。错误信息非常明确,指向了country字段。但问题往往不在于