Har1sh-k/claude-buddy-picker
GitHub: Har1sh-k/claude-buddy-picker
通过逆向 Claude Code 伙伴系统的哈希链路,使用正确的 Wyhash 算法暴力破解身份 UUID,让用户可以精确选择想要的伙伴物种和稀有度。
Stars: 2 | Forks: 0
# Claude Buddy Picker
**挑选你的 Claude Code 伙伴。不要把这件事交给运气。**
你的伙伴并不是随机的——它是通过种子伪随机数生成器 (PRNG) 根据你的身份信息确定性生成的。相同的身份 = 相同的伙伴,每次都是如此。这个工具可以让你准确选择你想要的物种和稀有度。
## 关键发现
目前所有的指南和脚本都在使用 **FNV-1a** 作为哈希函数。但对于原生安装来说,它们都是错的。
Claude Code 的原生二进制文件嵌入了 **Bun 运行时**。Bun 使用 `Bun.hash()` (Wyhash) 而不是 FNV-1a。如果你使用 FNV-1a 暴力破解出一个 ID 并应用它,你将会得到一个完全出乎意料的伙伴。
```
npm install (Node.js) native binary (Bun)
──────────────────── ───────────────────
Hash function: FNV-1a (32-bit) Bun.hash() → Wyhash
Result: ❌ Wrong buddy ✅ Correct buddy
```
**该工具使用执行它的任何运行时的哈希函数。** 在 `bun` 下运行适用于原生 Claude Code 安装(大多数用户),而在 `node` 下运行适用于 npm 安装。如果你的运行时与你的 Claude Code 安装不匹配,它会向你发出警告。
## 快速开始
### 前置条件
安装 [Bun](https://bun.sh)(原生 Claude Code 正确进行哈希计算所必需):
```
# macOS / Linux
curl -fsSL https://bun.sh/install | bash
# Windows (PowerShell)
irm bun.sh/install.ps1 | iex
```
### 交互模式
```
bun picker.js
```
```
╔════════════════════════════════════════════════╗
║ Claude Buddy Picker ║
║ Pick your companion. Don't leave it to chance. ║
╚════════════════════════════════════════════════╝
Runtime: bun 1.3.11 → Bun.hash (Wyhash)
Install: native (~/.local/bin/claude)
Buddy: 🐱 common cat named Whiskers
What would you like to do?
1. Pick a new buddy Find your perfect companion
2. Check any ID See what an ID produces
3. View current buddy Show your active companion
4. Setup persistence Survive re-logins
5. Exit
```
### 快速挑选(非交互模式)
```
# 寻找传说中的龙
bun picker.js --quick dragon
# 验证 ID 生成的内容
bun picker.js --verify 18b852ac-df26-44ed-9a3f-d8992a0760f5
# 检查当前的 buddy
bun picker.js --verify
```
## 工作原理
### 算法原理
```
identity + "friend-2026-401" → hash → Mulberry32 PRNG seed
│
┌───────────┼──────────────┐
▼ ▼ ▼
rarity species eye/hat/stats
```
1. **身份解析**:`oauthAccount.accountUuid ?? userID ?? "anon"`
2. **哈希处理**:使用 **Bun.hash**(原生)或 **FNV-1a**(npm)对身份 + 盐值进行哈希
3. **PRNG(伪随机数生成器)**:哈希值作为 Mulberry32 生成器的种子
4. **掷点生成**:按顺序消耗 PRNG → 稀有度 → 物种 → 眼睛 → 帽子 → 闪光 → 属性
### 存储的内容与重新生成的内容对比
| 字段 | 存储在配置中? | 来源 |
|-------|:-:|--------|
| `name` | 是 | 孵化时由 AI 生成 |
| `personality` | 是 | 孵化时由 AI 生成 |
| `rarity`, `species`, `stats` | **否** | 每次从身份哈希中重新生成 |
你**不能**通过编辑配置来获得传说级伙伴。该工具通过暴力破解出一个身份,从而确定性地产出你想要的伙伴。
### 物种
| | | | |
|---|---|---|---|
| 🦆 鸭子 | 🦢 鹅 | 🫧 泡泡 | 🐱 猫 |
| 🐉 龙 | 🐙 章鱼 | 🦉 猫头鹰 | 🐧 企鹅 |
| 🐢 乌龟 | 🐌 蜗牛 | 👻 幽灵 | 🦎 蝾螈 |
| 🦫 水豚 | 🌵 仙人掌 | 🤖 机器人 | 🐰 兔子 |
| 🍄 蘑菇 | 🐾 胖墩 | | |
### 稀有度
| 稀有度 | 概率 | 星级 |
|--------|-------------|-------|
| 普通 | 60% | ★ |
| 罕见 | 25% | ★★ |
| 稀有 | 10% | ★★★ |
| 史诗 | 4% | ★★★★ |
| 传说 | 1% | ★★★★★ |
## `accountUuid` 问题
如果你使用的是 Team 或 Pro 计划,你的配置中会包含一个 `oauthAccount.accountUuid`,它**优先于** `userID`:
```
oauthAccount?.accountUuid ?? userID ?? "anon"
```
该工具通过暴力破解一个 **UUID**(而不是十六进制字符串)并将其直接设置为 `accountUuid` 来解决这个问题。这比删除 `accountUuid` 更可靠,因为 OAuth 流程可能会重新添加它。
### 为什么暴力破解出的 UUID 会保留下来
如果配置的 `oauthAccount` 已经包含 `billingType`、`accountCreatedAt` 和 `subscriptionCreatedAt`,OAuth 启动流程就会**完全跳过个人资料获取**(提前返回)。这意味着它永远不会覆盖我们暴力破解出的 `accountUuid`——只要这三个字段存在,我们设置的值就会在重启后继续保留。
### 为什么以前的方法会失败
| 方法 | 问题 |
|----------|---------|
| 设置 `userID` + 删除 `accountUuid` | OAuth 流程在启动时会在内存中重新添加 `accountUuid` |
| 使用 FNV-1a 进行暴力破解 | 原生二进制文件使用的是 Bun.hash,而不是 FNV-1a |
| 在配置中编辑 `rarity` | 骨骼是从身份哈希重新生成的,而不是存储的 |
### 持久化
应用后,如果你重新登录 (`claude login`),你的伙伴可能会还原。持久化脚本会添加一个 Shell 包装器,在每次启动时自动强制执行你选择的 UUID:
**Windows (PowerShell):**
```
.\platforms\windows\persist.ps1
```
**Windows (Git Bash):**
```
bash platforms/windows/persist-bash.sh
```
**Linux / macOS:**
```
bash platforms/unix/persist.sh
```
## 调查日志
以下是促成这些发现的完整调查过程。它最初只是“给我一条传说中的龙”的简单想法,后来却演变成对 Claude Code 内部机制的深入探究。
### 尝试 1:GitHub 上的脚本(错误的哈希算法)
一个[在 GitHub 上流传的脚本](https://github.com/anthropics/claude-code/discussions/2664)声称你可以使用 FNV-1a 暴力破解出一个 `userID` 并将其写入 `~/.claude.json`:
```
node reroll.js dragon 500000
# found: legendary dragon -> c3b62c7835f2f982fd65b9e5599eb36d7b7d93da96847d310645223f41120a60
```
我们在 `~/.claude.json` 中设置了 `userID` 并删除了 `companion` 字段。在重启并运行 `/buddy` 之后……我们得到了一个名叫 Baneshift 的**稀有幽灵**。而不是一条传说中的龙。
**失败原因:** 该脚本使用的是 FNV-1a,但原生 Claude Code 二进制文件使用的是 Bun.hash (Wyhash)。相同的 ID,完全不同的哈希值,完全不同的伙伴。
### 尝试 2:发现 accountUuid 的高优先级
我们深入研究了 Claude Code 源码(从 npm 包反混淆得到的 `cli.js`)并发现:
```
function ch1() {
let q = w8();
return q.oauthAccount?.accountUuid ?? q.userID ?? "anon";
}
```
身份解析顺序:
1. `oauthAccount.accountUuid`(如果使用 OAuth 登录)
2. `userID`(备选方案)
3. `"anon"`(最后手段)
Team/Pro 计划用户拥有 `accountUuid`,它**总是优先于** `userID`。即使你设置了一个完美的 `userID`,伙伴系统也会忽略它。
### 尝试 3:删除 accountUuid(部分成功)
关键发现:`??`(空值合并)操作符在遇到 `undefined` 时会穿透。如果 `accountUuid` 不存在,表达式将计算为 `userID`。
我们从配置中删除了 `accountUuid`,同时保持 `oauthAccount` 的其余部分完好无损。伙伴系统本应该回退到 `userID`……
但我们**仍然得到了错误的伙伴**。FNV-1a 暴力破解出的 ID 产生的是“幽灵”而不是“龙”,因为哈希函数是错误的。
### 尝试 4:验证算法
我们通过 `npm pack @anthropic-ai/claude-code@2.1.89` 下载了实际的 Claude Code 源码,并比较了每一部分:
- **PRNG (Mulberry32)**:完全相同。相同的常量 `0x6d2b79f5`。
- **哈希算法 (FNV-1a)**:完全相同。相同的初始值 `2166136261`,相同的质数 `16777619`。
- **物种数组**:顺序完全相同。从 `String.fromCharCode` 混淆中解码得出。
- **稀有度权重**:完全相同。
一切都匹配。然而,结果却是错的。这让人深感困惑。
### 尝试 5:Bun.hash 的突破
然后我们注意到了 npm 包中的 `bun.lock` 文件。以及哈希函数中的这个分支:
```
function hashString(s) {
if (typeof Bun !== "undefined")
return Number(BigInt(Bun.hash(s)) & 0xffffffffn); // ← THIS BRANCH
// FNV-1a below — only used under Node.js
let h = 2166136261;
// ...
}
```
原生 Claude Code 二进制文件(238MB 的可执行文件)**嵌入了 Bun 运行时**。当哈希函数在该二进制文件中运行时,`typeof Bun !== "undefined"` 为真,因此它会使用 `Bun.hash()` (Wyhash)——这是一种与 FNV-1a 完全不同的算法。
我们安装了 Bun 并进行了测试:
```
// Under Bun:
Bun.hash("c3b62c78...friend-2026-401") → rare ghost // Matches Baneshift!
Bun.hash("603063fc...friend-2026-401") → common cactus // Matches Prickle!
// Under Node (FNV-1a):
fnv1a("c3b62c78...friend-2026-401") → legendary dragon // WRONG for native binary
```
每一次失败的尝试突然都变得合理了。我们一直使用 FNV-1a 暴力破解出的 ID *对于 Node.js 来说是正确的*,但*对于大多数人实际使用的原生二进制文件来说则是完全错误的*。
### 尝试 6:最终修复
在安装了 Bun 之后,我们使用 `Bun.hash` 暴力破解出了一个 UUID:
```
bun -e "/* brute-force with Bun.hash */..."
# found: legendary dragon -> 18b852ac-df26-44ed-9a3f-d8992a0760f5
```
将其设置为 `accountUuid`(而不是 `userID`——因为 `accountUuid` 具有更高的优先级且不会被忽略)。删除 `companion` 以强制重新孵化。重启 Claude Code。
结果:**Kiln,一顶戴着巫师帽的传说中的龙**。终于成功了。
### 源码参考
所有发现均基于反混淆的 Claude Code 源码(`@anthropic-ai/claude-code` v2.1.89):
- **身份解析**:`companionUserId()` → `oauthAccount?.accountUuid ?? userID ?? "anon"`
- **哈希函数**:`hashString()` → `Bun.hash()`(原生)或 FNV-1a(npm)
- **骨骼生成**:`roll()` → `rollFrom()` → `rollRarity()` + `pick(SPECIES)`
- **存储的内容**:`StoredCompanion = { name, personality, hatchedAt }`
- **重新生成的内容**:`CompanionBones = { rarity, species, eye, hat, shiny, stats }`
- **OAuth 写入**:`VZ6()` → `R8()` 写入包含 `accountUuid` 在内的 `oauthAccount`
- **OAuth 跳过条件**:如果 `billingType`、`accountCreatedAt`、`subscriptionCreatedAt` 都存在,启动流程将跳过个人资料获取——从而保留我们暴力破解出的 UUID
## 深入探讨
### 原生二进制文件如何使用 Bun
Claude Code 的 npm 包(`@anthropic-ai/claude-code`)包含以下哈希函数:
```
function hashString(s) {
if (typeof Bun !== "undefined")
return Number(BigInt(Bun.hash(s)) & 0xffffffffn); // ← Wyhash
// FNV-1a fallback for Node.js
let h = 2166136261;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
```
原生二进制文件(238MB 的 PE/ELF/Mach-O 可执行文件)将 Bun 作为其 JS 运行时嵌入。证据如下:
- npm 包中存在 `bun.lock`
- 物种名称使用 `String.fromCharCode` 进行了混淆(Bun 打包器的模式)
- Node 的 FNV-1a 产生错误结果;Bun.hash 产生正确结果(经验证)
### 伙伴内部机制
**反应系统**:在每次 Claude 响应之后,`fireCompanionObserver` 会将你最近的转录内容(最多 5000 个字符)发送到一个 API 端点:
```
POST /api/organizations/{orgId}/claude_code/buddy_react
Body: { name, personality, species, rarity, stats, transcript, reason, recent, addressed }
```
服务器返回一句简短的俏皮话,显示在伙伴的对话气泡中。你的伙伴*不是* Claude——它是一个拥有自己 API 的独立系统。
**属性影响性格生成。** 在孵化时,`inspirationSeed` 和属性(例如 `CHAOS:100 DEBUGGING:80`)会被发送给一个 AI 模型,用于生成名称和性格文本。高 CHAOS 属性往往会产生混乱的性格。
**动画系统:**
- 每个物种都有 **3 个动画帧**(静止、小动作、特殊效果)
- 刷新率:**500ms**
- 空闲循环:`[0,0,0,0,1,0,0,0,-1,0,0,2,0,0,0]`,其中 `-1` = 眨眼
- 当做出反应或被抚摸时:快速循环播放所有帧
**对话气泡:**
- 显示大约 **10 秒**(20 个刻度)
- 最后 **~3 秒** 淡出(变暗的文本)
- `/buddy pet` 会触发 **2.5 秒**的漂浮爱心动画
**用名字称呼:** 伙伴介绍会被注入到 Claude 的系统提示词中:
所以当你输入“Kiln what do you think?”时,Claude 会退居幕后,由伙伴的气泡通过反应 API 进行回答。
**帽子**:只有非普通稀有度才能获得帽子:王冠、高顶礼帽、螺旋桨、光环、巫师帽、绒线帽、tinyduck(一只坐在它头上的小鸭子)。
**窄终端:** 如果你的终端列数少于 100,精灵图会折叠成单行的表情:
| 物种 | 折叠后 |
|---------|-----------|
| 猫 | `=·ω·=` |
| 龙 | `<·~·>` |
| 幽灵 | `(·o·)` |
| 泡泡 | `{·_·}` |
**功能标志**:整个系统由 `feature('BUDDY')` 控制。Anthropic 可以随时在服务器端禁用它。
**愚人节起源:** 彩虹 `/buddy` 预告通知只会在 **2026 年 4 月 1 日至 7 日** 期间出现。盐值 `friend-2026-401` 了 4 月 1 日的发布日期。在预告窗口期之后,命令依然有效,但启动通知会消失。
## 项目结构
```
claude-buddy-picker/
├── picker.js # Interactive CLI
├── lib/
│ ├── buddy.js # Hash, PRNG, roll algorithm
│ └── config.js # Config read/write/apply
├── platforms/
│ ├── windows/
│ │ ├── persist.ps1 # PowerShell persistence
│ │ └── persist-bash.sh # Git Bash persistence
│ └── unix/
│ └── persist.sh # Linux/macOS persistence
├── package.json
└── LICENSE (MIT)
```
## 常见问题
**问:我的伙伴会进化或升级吗?**
答:不会。没有进度系统。属性由你的身份哈希固定。伙伴会对你的代码做出情境化的反应(通过 API),但没有任何东西会永久改变。
**问:我可以直接在 `~/.claude.json` 中编辑稀有度吗?**
答:不行。骨骼(稀有度、物种、属性)在每次读取时都会从你的身份重新生成。源代码明确指出:*“用户不能通过编辑来获得传说级。”*
**问:为什么我需要 Bun?我可以用 Node 吗?**
答:如果你将 Claude Code 安装为**原生二进制文件**(大多数人的情况),它在内部使用的是 Bun.hash。在 Node 下运行此工具将使用 FNV-1a——一种不同的哈希函数——并且会产生给出错误伙伴的 ID。运行 `bun picker.js` 以使用正确的哈希。如果你通过 `npm i -g @anthropic-ai/claude-code` 安装了 Claude Code 并使用 Node 运行它,那么对你来说 Node/FNV-1a 是正确的。
**问:我怎么知道我安装的是原生二进制文件还是 npm 安装的?**
答:运行 `bun picker.js --verify`。它会自动检测并显示你的安装方法。或者检查:如果存在 `~/.local/bin/claude`(或 `claude.exe`)并且大小在 200MB 以上,那就是原生二进制文件。
**问:这会损坏我的 Team 计划吗?**
答:不会。该工具只修改你配置中的 `accountUuid` 和 `companion`。身份验证使用单独存储的 OAuth 令牌——计费、组织设置和其他所有内容都不会受到影响。
**问:这能在 Claude Code 更新后保留下来吗?**
答:你配置中的 `accountUuid` 在更新后会持续存在。但是,如果 Anthropic 更改了盐值(`friend-2026-401`)或算法,所有的伙伴掷点结果都将改变。你需要使用更新后的参数重新进行挑选。
**问:重新登录后我的伙伴还原了。发生了什么?**
答:OAuth 流程从服务器获取了你真实的 `accountUuid` 并覆盖了暴力破解出的那个。设置持久化(选择器中的选项 4,或运行平台脚本)以在每次启动时自动修复此问题。
**问:`/buddy pet` 会做什么特别的事吗?**
答:它会触发 2.5 秒的爱心动画和伙伴的反应。没有永久效果。
**问:如果我日常运行 Claude Code 时用 Bun 而不是 Node 会怎样?**
答:如果你通过 npm 安装了 Claude Code 并自己在 Bun 下运行它,那么将会使用 `Bun.hash()`。选择器可以正确处理这种情况——只需在与你的 Claude Code 安装相同的运行时下运行它即可。
**问:闪光有多稀有?**
答:1% 的几率,在物种和帽子之后掷点决定。闪光状态也是从身份重新生成的(而不是存储的),所以你不能伪造它。
**问:我能得到一只闪光的传说伙伴吗?**
答:可以,但每次掷点的几率约为 0.01%(1% 传说 * 1% 闪光)。暴力破解大约需要 1000 万次以上的尝试。增加最大尝试次数:`bun picker.js --quick dragon 20000000`
**问:每项属性有什么用?**
答:属性会影响孵化时由 AI 生成的性格。高 CHAOS 往往会产生混乱的名字/性格。高 SNARK 会产生尖酸刻薄的伙伴。它们不影响游戏玩法——因为根本没有游戏玩法。
## 许可证
MIT
标签:AI伴侣, Bun, Claude Code, CMS安全, FNV-1a, GNU通用公共许可证, JavaScript, MITM代理, Node.js, PoC, PRNG, VPS部署, Wyhash, 个性化设置, 代码伙伴, 伪随机数生成器, 加密散列, 哈希算法, 威胁情报, 开发者工具, 数字取证, 数据可视化, 暴力破解, 确定性生成, 编程工具, 脚本, 自动化脚本, 自定义脚本, 身份哈希, 软件分析, 运行时, 远程代码执行, 逆向分析