1. 凌晨两点,我盯着滚动的终端,决定给 Claude Code 配个实体监工
Claude Code CLI 是个好东西,工具自动执行、Bash 命令一条接一条跑,效率确实高。但问题也在这儿:它太能跑了。你敲完claude回车,终端就开始刷日志,Bash、Write、Edit 轮番上阵,你根本不知道它现在是在干活、卡住了,还是正准备执行一条rm -rf级别的危险命令。审批提示一闪而过,被日志刷过去是常事。
我试过把终端窗口缩小放一边,也试过开第二个 pane 专门盯日志,但人眼盯滚动文本这件事本身就反人性。于是我想,能不能把 Claude Code 的状态投射到一个物理设备上——一个放在桌角的 ESP32 小屏幕,用 BLE 跟 PC 通信,Claude 忙的时候它皱眉,等审批的时候它瞪眼,任务完成它跳个爱心。这就是「电子宠物监工搭子」的由来。
这篇不是概念文,是能跟做的搭建记录。核心要解决两件事:一是多工具 Key 分散的问题,用 TaoToken 统一 Key 收口;二是状态不可见的问题,用 ESP32 + BLE + asyncio 把 Claude Code CLI 的状态实时投射到实体屏幕。适合已经在用 Claude Code、手头有块 ESP32、想折腾点实体交互的开发者。下面从 Key 接入开始,一步步到 BLE 状态回传验证。
2. 前置:TaoToken 统一 Key 与 Claude Code 的接入位置
在动手写 ESP32 代码之前,先把 Key 这件事理清楚。Claude Code CLI 本身支持通过环境变量或配置文件指定 API 端点,但如果你同时用多个工具(Claude Code、Cursor、各种 CLI Agent),每个都配一遍 Key,管理起来很乱。TaoToken 的作用就是提供一个统一的 Key 和端点,让这些工具都指向同一个入口。
TaoToken 的官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api (这个不加 UTM)。你需要先去控制台创建一个 API Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 列表在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。拿到 Key 之后,Claude Code 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各工具的配置示例。
这里要强调一点:TaoToken 是合规的 API 聚合入口,不是那种灰色中转,你配置的时候走的是标准 HTTP 端点,Claude Code 的请求格式不变,只是 base_url 和 key 换一下。对于 Claude Code 这种 CLI 工具,最稳的方式是通过settings.json或环境变量注入,而不是改源码。
3. 可复制配置:config.toml、settings.json 与 CC Switch 片段
Claude Code CLI 的配置分两层:一层是全局的~/.claude/settings.json,一层是项目级的.claude/settings.json。我建议把 TaoToken 的 Key 放在全局配置里,项目级只覆盖模型名之类的参数。下面是一个可复制的settings.json骨架:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoTokenKey", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" }, "permissions": { "allow": [ "Bash(git status)", "Bash(ls)", "Read" ], "ask": [ "Bash(rm *)", "Write", "Edit" ] } }注意permissions.ask这一块,它决定了哪些操作会触发审批。我们的电子宠物监工就是监听这些审批事件。如果你用的是 CC Switch 来管理多套配置,可以在它的配置片段里这样写:
# ~/.cc-switch/config.toml [profiles.taotoken] name = "TaoToken 统一入口" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" default_model = "claude-sonnet-4-20250514" [profiles.taotoken.env] ANTHROPIC_BASE_URL = "https://taotoken.net/api" ANTHROPIC_API_KEY = "${TAOTOKEN_API_KEY}"这样你在 CC Switch 里切换 profile 的时候,Claude Code 会自动读取对应的端点。如果你不用 CC Switch,直接在 shell 里 export 也行:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的TaoTokenKey"配置完之后,先别急着接 ESP32,用一条最简单的请求验证 Key 是否生效。Claude Code 的模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite ,你可以先在网页端确认 Key 能正常调用模型,再去配 CLI。
4. 验证请求:确认 Claude Code 走的是 TaoToken 端点
配置写完之后,怎么确认 Claude Code 真的走了 TaoToken 而不是默认端点?最直接的办法是看请求日志。Claude Code 在 debug 模式下会打印实际请求的 URL。你可以这样启动:
claude --debug然后在另一个终端里触发一个简单操作,比如让它读一个文件。如果 debug 输出里出现https://taotoken.net/api/v1/messages这样的 URL,说明配置生效了。如果还是api.anthropic.com,那说明环境变量没被读取,检查一下settings.json的路径对不对,或者 shell 里有没有覆盖。
另一个验证方式是看 TaoToken 控制台的用量统计。你调用一次之后,控制台的请求计数应该会增加。如果计数不动,说明请求没打到 TaoToken。这一步很重要,因为后面 ESP32 监工依赖的是 Claude Code 的 Hook 事件,如果 Key 配置错了,Hook 根本不会触发。
验证通过之后,你可以顺手测一下模型对话,确认返回内容正常。TaoToken 的模型对话页面在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite ,选 Claude 系列模型发一条消息,看看响应速度和内容质量。这一步没问题,就可以进入 ESP32 部分了。
5. ESP32 + BLE + asyncio:状态回传与审批拦截的完整链路
现在进入硬件部分。整体架构是这样的:Claude Code CLI 通过 Hook 把状态事件发给一个 PC 端的守护进程(Python 写的),守护进程通过 BLE 把状态推给 ESP32,ESP32 收到后更新屏幕动画。反过来,ESP32 上的触控按钮被按下时,通过 BLE 把审批结果回传给守护进程,守护进程再通知 Claude Code 放行或拒绝。
先看 PC 端守护进程的核心逻辑。用 Python 的bleak库做 BLE 通信,用asyncio做异步调度:
import asyncio import json from bleak import BleakServer, BleakCharacteristic NUS_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" NUS_RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" NUS_TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" class BuddyServer: def __init__(self): self.clients = set() self.pending_approval = None async def send_state(self, state: str, detail: str = ""): payload = json.dumps({"state": state, "detail": detail}) + "\n" data = payload.encode("utf-8") # BLE NUS 单包 20 字节,分片发送 for i in range(0, len(data), 20): chunk = data[i:i+20] for client in self.clients: await client.write_gatt_char(NUS_TX_UUID, chunk) async def on_approval(self, approved: bool): if self.pending_approval: self.pending_approval.set_result(approved) self.pending_approval = NoneESP32 端用 MicroPython 的aioble库,开三个 asyncio 任务:一个处理 BLE 连接和消息收发,一个监听触控,一个以 20FPS 渲染动画。关键点是 BLE 分片的透明拼接——ESP32 收到的数据可能是半截 JSON,需要按\n做帧边界:
import asyncio import aioble import json buffer = "" async def ble_task(): global buffer while True: connection = await aioble.advertise(...) async for data in connection.characteristic(NUS_RX_UUID).subscribed(): buffer += data.decode("utf-8") while "\n" in buffer: line, buffer = buffer.split("\n", 1) msg = json.loads(line) handle_message(msg)handle_message里根据state字段切换动画:idle闭眼打哈欠,busy皱眉工作,approval瞪眼等确认。审批界面带 30 秒倒计时,超时自动放行,避免卡住 Claude Code 的任务。
触控任务单独跑,检测到按钮按下就通过 BLE 回传{"approval": true}或{"approval": false}。渲染任务固定 20FPS,用asyncio.sleep(0.05)控制节奏,保证动画不掉帧。
6. 本篇常见错排查:BLE 连不上、JSON 截断、审批超时
第一个坑是 BLE 连不上。ESP32 的 NUS 服务 UUID 必须和 PC 端一致,很多人抄代码的时候只改了服务 UUID 没改特征 UUID,导致订阅失败。检查NUS_RX_UUID和NUS_TX_UUID是否和 ESP32 端定义的一致。另外,Windows 上 BLE 需要先配对一次,Linux 上可能需要bluetoothctl手动 trust。
第二个坑是 JSON 截断。BLE NUS 单包 20 字节是硬限制,如果你的 JSON 超过 20 字节(比如带 detail 字段),必须分片。分片的时候注意帧头标记,我用的方案是每条消息以\n结尾,接收端按\n切分。如果你发现收到的 JSON 解析报错,先打印原始 buffer 看看是不是半截。
第三个坑是审批超时。ESP32 上的倒计时如果没做,Claude Code 会一直等,任务卡死。我的做法是 30 秒无操作自动放行,同时在 PC 端守护进程里也设一个超时,双保险。如果你发现 Claude Code 卡在审批界面不动,检查 ESP32 是否还在线,BLE 断连的时候守护进程应该自动放行。
第四个坑是 Key 配置没生效。前面验证请求那一步如果跳过了,后面 Hook 事件可能根本不触发。Claude Code 的 Hook 需要在settings.json里显式开启,确认hooks字段配置正确。如果 Hook 不触发,ESP32 永远停在 idle 状态。
7. 长期编码与 Agent 场景:Coding Plan 与后续扩展
如果你打算长期用 Claude Code 跑编码任务,或者把电子宠物监工扩展到多设备、多 Agent 场景,建议关注一下 TaoToken 的 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它适合需要稳定调用、长期跑 Agent 的开发者,Key 管理和用量统计比单次调用更清晰。
后续扩展方向有几个:一是把审批结果同步到手机,用 BLE 中继或者 MQTT;二是加个蜂鸣器,审批请求来的时候响一声,避免你戴着耳机错过;三是把状态数据存到本地 SQLite,跑一周之后看看 Claude Code 到底在哪些操作上花时间最多。这些都不难,核心链路通了之后,剩下的就是加外设和写逻辑。
回到最初的问题:为什么要给 Claude Code 配个实体监工?因为终端里的日志是流动的,而物理屏幕上的状态是凝固的。你瞟一眼就知道它在干嘛,这种确定感是滚动日志给不了的。ESP32 不贵,BLE 也不复杂,花一个周末把这条链路搭通,之后每次敲claude的时候,桌角那只小猫都会替你盯着。