1. 从「预制娃」到亲子 vibe coding:我为什么选 Claude Code + TaoToken
刷到「预制娃」这个词的时候,我正陪儿子在客厅地板上拼乐高。初二暑假自学完初三课程、整天刷中考真题、计划提前学高中内容——评论区一零后们晒进度晒得热火朝天,我一时有点恍惚:我们八零后的暑假明明是上午睡懒觉看《西游记》,下午出门找同学打 FC,晚上接着看电视,作业几乎为零。
我不是要评判谁对谁错,只是觉得「兴趣」这件事被挤得太靠后了。我小学开始玩游戏,为了看懂英文界面和过场,自己抱着词典啃,后来四六级轻松过,再后来进了外企。兴趣才是最好的老师,这句话在我身上是实打实验证过的。
所以儿子暑假的英语单词预习,我没打算下个现成 App 了事。某词斩、某利说这类工具我也用过,玩法太「答题」了,孩子坐不住。我决定自己手搓一个——用 Claude Code 做主力编码助手,通过 TaoToken 统一 Key 和 API 通道接入模型,技术栈就是最朴素的 HTML + Node.js。目标很明确:做一个射击类单词闯关小游戏,让孩子在玩的过程中,顺便理解「前端页面」和「后端接口」到底是怎么协作的。
这篇文章我会把整套流程拆开:TaoToken 怎么配、Claude Code 的 settings.json 和 config.toml 骨架长什么样、CC Switch 怎么切、本地怎么启动和验证、以及我踩过的坑。你照着做,一个下午就能和孩子跑起来第一个版本。
2. TaoToken 前置:统一 Key 与 API 通道,别让配置劝退
先说清楚 TaoToken 在这里的角色。它是一个统一的模型 API 接入平台,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。你可以把它理解成一个「统一插座」:Claude Code、各种 Agent 工具、自己写的 Node 脚本,都通过同一个 Key 和同一个 Base URL 去调用模型,不用每个工具单独配一套凭证。
对亲子项目来说,这一点特别重要。因为孩子参与的时候,最怕的就是「配置」这一步卡住——他不懂什么是环境变量,也不懂为什么这个工具要填这个字段那个工具要填那个字段。统一通道之后,我只需要维护一份 Key,Claude Code 用它,我后面写的 Node 后端也用它,切换模型或者换工具的时候,改一处就行。
具体操作路径是这样的:先打开官网注册并登录,进入控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 生成一个 Key。这个 Key 就是后面所有配置里要填的东西,格式通常是一串以特定前缀开头的字符串。生成之后先复制到本地一个安全的地方,别直接贴在聊天窗口或者截图里。
注意:Key 只显示一次,页面刷新后就看不到了。如果没存下来,直接删掉重新生成一个,不要试图找回。
拿到 Key 之后,Claude Code 这边有两种接入方式:一种是走 settings.json 的环境变量配置,一种是走 config.toml 的 provider 配置。我两种都试过,下面分别给骨架。如果你只是想先跑通,用 settings.json 就够了;如果你后面要接多个模型或者做更细的路由,config.toml 更合适。
3. 可复制配置:settings.json 与 config.toml 骨架
3.1 settings.json 骨架
Claude Code 读取的 settings.json 一般放在用户目录下的.claude文件夹里。Windows 是C:\Users\你的用户名\.claude\settings.json,macOS 和 Linux 是~/.claude/settings.json。如果文件夹不存在就自己建一个。
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514", "ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-20250514" }, "permissions": { "allow": [ "Read", "Write", "Edit", "Bash(npm:*)", "Bash(node:*)", "Bash(git:*)" ] } }几个字段解释一下。ANTHROPIC_BASE_URL指向 TaoToken 的 API 入口,注意这里不带任何 UTM 参数,就是干净的https://taotoken.net/api。ANTHROPIC_AUTH_TOKEN填你刚才生成的 Key。ANTHROPIC_MODEL是主模型,负责写代码和规划;ANTHROPIC_SMALL_FAST_MODEL是快速小模型,负责一些轻量任务,比如补全、格式化。permissions.allow里我放开了 npm、node、git 这几类命令,因为做游戏项目要频繁跑npm start、node server.js、git commit,每次弹权限确认太打断节奏。
提示:如果你不确定模型名称,可以先只填 BASE_URL 和 AUTH_TOKEN,模型字段留空,Claude Code 会用默认值。跑通之后再回来指定具体模型。
3.2 config.toml 骨架
如果你用的是支持 config.toml 的客户端或者自己写的 Agent 框架,配置长这样:
[provider.taotoken] name = "taotoken" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoToken密钥" model = "claude-sonnet-4-20250514" small_model = "claude-haiku-4-20250514" max_tokens = 8192 temperature = 0.7 [agent] provider = "taotoken" working_dir = "./word-shooter" auto_approve = ["read", "write", "edit", "bash:npm", "bash:node"]这个骨架的好处是结构清晰,provider 和 agent 分开。后面如果你想加第二个 provider,比如本地跑个小模型做兜底,直接再加一个[provider.local]段就行,agent 那边改一行provider = "local"就切过去了。
3.3 CC Switch 切换步骤
CC Switch 是一个用来在多个 Claude Code 配置之间快速切换的小工具。我为什么需要它?因为我在公司用的是另一套 API 配置,回家做亲子项目要用 TaoToken,如果每次都手动改 settings.json,很容易改错。
操作步骤:
第一步,安装 CC Switch。它通常是一个命令行工具,装好之后会在用户目录下维护一个 profiles 列表。
第二步,把当前 TaoToken 配置保存成一个 profile。命令大概是ccswitch save taotoken-home,它会把你现在的 settings.json 快照下来。
第三步,再保存一个公司配置的 profile,比如ccswitch save company。
第四步,切换的时候直接ccswitch use taotoken-home,它会自动把对应的 settings.json 写回去。切完可以ccswitch list确认当前激活的是哪个。
注意:切换之后最好重启一下 Claude Code 会话,因为环境变量是在启动时读取的,热切换不一定生效。
4. 本地启动与验证:从零跑通第一个请求
4.1 项目初始化
先建项目目录,我给它起名word-shooter:
mkdir word-shooter && cd word-shooter npm init -y npm install express然后建三个核心文件:server.js、public/index.html、public/game.js。目录结构长这样:
word-shooter/ ├── package.json ├── server.js └── public/ ├── index.html ├── game.js └── words.json4.2 server.js:后端接口骨架
const express = require('express'); const path = require('path'); const app = express(); const PORT = 3000; app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); const scores = []; app.post('/api/score', (req, res) => { const { name, score, difficulty } = req.body; if (!name || typeof score !== 'number') { return res.status(400).json({ ok: false, msg: '参数不完整' }); } scores.push({ name, score, difficulty, time: Date.now() }); res.json({ ok: true, rank: scores.length }); }); app.get('/api/score', (req, res) => { const top = [...scores].sort((a, b) => b.score - a.score).slice(0, 10); res.json({ ok: true, list: top }); }); app.listen(PORT, () => { console.log(`游戏服务已启动:http://localhost:${PORT}`); });这个后端只做两件事:接收成绩写入、返回排行榜。孩子玩完之后,前端fetch('/api/score')就能看到自己排第几。这就是让他理解「前端发请求、后端存数据」最直观的例子。
4.3 index.html:前端页面骨架
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>单词射击闯关</title> <style> body { margin: 0; background: #0a0e1a; color: #e0f0ff; font-family: sans-serif; } #hud { display: flex; gap: 24px; padding: 12px 20px; font-size: 18px; } #game { display: block; width: 100vw; height: calc(100vh - 60px); } #start-screen { position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; background: rgba(10,14,26,0.95); } button { padding: 10px 28px; font-size: 18px; cursor: pointer; } </style> </head> <body> <div id="hud"> <span>得分:<b id="score">0</b></span> <span>连击:<b id="combo">0</b></span> <span>掌握:<b id="mastered">0</b></span> </div> <canvas id="game"></canvas> <div id="start-screen"> <h2>单词射击闯关</h2> <input id="username" placeholder="输入你的名字" /> <div> <button>const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); let score = 0, combo = 0, mastered = 0; function resize() { const dpr = window.devicePixelRatio || 1; canvas.width = window.innerWidth * dpr; canvas.height = (window.innerHeight - 60) * dpr; ctx.scale(dpr, dpr); } window.addEventListener('resize', resize); resize(); const words = [ { en: 'apple', zh: '苹果' }, { en: 'bridge', zh: '桥' }, { en: 'courage', zh: '勇气' }, { en: 'dragon', zh: '龙' }, ]; let current = null; let enemies = []; function spawn() { current = words[Math.floor(Math.random() * words.length)]; enemies = [ { x: 200, y: 150, text: current.en, correct: true }, { x: 500, y: 200, text: words[Math.floor(Math.random() * words.length)].en, correct: false }, { x: 350, y: 320, text: words[Math.floor(Math.random() * words.length)].en, correct: false }, ]; } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#e0f0ff'; ctx.font = '28px sans-serif'; ctx.fillText(`请击落:${current ? current.zh : ''}`, 40, 60); enemies.forEach(e => { ctx.beginPath(); ctx.arc(e.x, e.y, 40, 0, Math.PI * 2); ctx.fillStyle = e.correct ? '#1e3a5f' : '#3a1e2f'; ctx.fill(); ctx.fillStyle = '#fff'; ctx.font = '16px sans-serif'; ctx.fillText(e.text, e.x - 24, e.y + 6); }); } canvas.addEventListener('click', ev => { const rect = canvas.getBoundingClientRect(); const mx = ev.clientX - rect.left; const my = ev.clientY - rect.top; enemies.forEach(e => { const d = Math.hypot(mx - e.x, my - e.y); if (d < 40) { if (e.correct) { score += 10; combo += 1; mastered += 1; document.getElementById('score').textContent = score; document.getElementById('combo').textContent = combo; document.getElementById('mastered').textContent = mastered; spawn(); } else { combo = 0; document.getElementById('combo').textContent = combo; } } }); }); document.getElementById('start-btn').addEventListener('click', () => { const name = document.getElementById('username').value.trim(); if (!name) { alert('先输入名字'); return; } document.getElementById('start-screen').style.display = 'none'; spawn(); loop(); }); function loop() { draw(); requestAnimationFrame(loop); }4.5 启动与验证
npm start如果 package.json 里还没配 start 脚本,先加上:
"scripts": { "start": "node server.js" }启动后终端会打印游戏服务已启动:http://localhost:3000。浏览器打开这个地址,输入名字,点「初中」,就能看到中文提示和三个英文单词圆圈。点对了加分,点错了连击清零。
验证后端接口是否通,另开一个终端:
curl -X POST http://localhost:3000/api/score \ -H "Content-Type: application/json" \ -d '{"name":"test","score":100,"difficulty":"easy"}'返回{"ok":true,"rank":1}就说明后端写入正常。再curl http://localhost:3000/api/score能看到排行榜列表。
验证 TaoToken 通道是否通,可以在 Claude Code 里直接问一句「帮我给 game.js 加一个倒计时功能」,看它能不能正常返回代码。如果返回了,说明 settings.json 里的 BASE_URL 和 Key 都生效了。
5. 本篇常见错排查
5.1 401 或 invalid api key
最常见的原因是 Key 复制的时候带了空格,或者把sk-前缀漏掉了。检查 settings.json 里ANTHROPIC_AUTH_TOKEN的值,前后不能有空格。另外确认你用的是 TaoToken 控制台生成的 Key,不是别的平台的。
5.2 连接超时或 ECONNREFUSED
先确认ANTHROPIC_BASE_URL写的是https://taotoken.net/api,注意结尾没有多余的斜杠。如果本地网络环境有特殊设置,先试试curl https://taotoken.net/api能不能通。另外 Claude Code 的配置改完之后要重启会话,环境变量不会热加载。
5.3 npm start 报端口占用
3000 端口被别的项目占了。改 server.js 里的PORT常量,比如改成 3001,然后重新npm start。或者用lsof -i :3000(macOS/Linux)或netstat -ano | findstr :3000(Windows)找到占用进程处理掉。
5.4 前端点击没反应
打开浏览器开发者工具看 Console。最常见的是game.js加载顺序问题——如果game.js在words.json之前加载,words数组还是空的。确保words.json先 fetch 完成,再动态插入game.js。另一个可能是 canvas 的getBoundingClientRect在高分屏下坐标偏移,检查resize()里 DPR 缩放有没有正确应用。
5.5 CC Switch 切换后配置没生效
CC Switch 写回 settings.json 之后,Claude Code 需要重启。另外确认你切换的 profile 里 Key 是完整的,有时候快照保存的时候 Key 被截断了。用ccswitch list看一下当前激活的 profile 名称对不对。
6. 接下来怎么走:让孩子自己提需求
第一个版本跑通之后,我建议你不要急着加功能,先让孩子玩十分钟,然后问他「你想改哪里」。我儿子当时说「单词太少了」「想要爆炸效果」「想要排行榜显示在游戏里」。这三个需求,正好对应三个学习点:词库扩展(JSON 数据结构)、粒子效果(Canvas 绘制)、排行榜接口(前后端协作)。
你可以带着他一起在 Claude Code 里输入这些需求,让模型改代码,然后npm start看效果。改错了就git checkout回滚,改对了就git commit存一版。这个过程本身就是最好的编程启蒙——不是教他语法,而是让他看到「想法 → 代码 → 运行 → 反馈」的完整闭环。
如果你后面想把这个项目做成长期维护的 Agent 工作流,比如让 Claude Code 自动跑测试、自动提交、自动部署,可以了解一下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合这种持续迭代的场景。如果只是想先验证模型对话效果,可以直接用模型对话 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 页面试几句。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,Claude Code 相关的配置说明在 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。
最后说一个我自己的体会:这个游戏现在还很简陋,画面就是几个圆圈,音效也没有,但我儿子每天真的会打开玩几局。他记住的不是「苹果是 apple」这种死记硬背,而是「我点对了那个圆圈,分数涨了,排行榜上有我的名字」。这种即时反馈带来的动力,比任何预习作业都管用。而我在旁边做的,只是把配置配好,把通道打通,剩下的交给他和 Claude Code 去折腾。