add buddy.mjs: standalone BUDDY pet hatching from leaked source algorithms

This commit is contained in:
Anton Abyzov 2026-04-01 02:09:29 -04:00
parent dd7b5bc7d3
commit d1f116c610

265
buddy.mjs Normal file
View File

@ -0,0 +1,265 @@
#!/usr/bin/env node
// BUDDY Pet System — standalone hatching card for Claude Code v2.1.88
// Replicates the companion generation from the leaked source
import { readFileSync, writeFileSync, existsSync } from 'fs'
import { homedir } from 'os'
import { join } from 'path'
// ── Species (encoded to avoid build-time codename stripping) ──
const c = String.fromCharCode
const SPECIES = [
c(0x64,0x75,0x63,0x6b), // duck
c(0x67,0x6f,0x6f,0x73,0x65), // goose
c(0x62,0x6c,0x6f,0x62), // blob
c(0x63,0x61,0x74), // cat
c(0x64,0x72,0x61,0x67,0x6f,0x6e), // dragon
c(0x6f,0x63,0x74,0x6f,0x70,0x75,0x73), // octopus
c(0x6f,0x77,0x6c), // owl
c(0x70,0x65,0x6e,0x67,0x75,0x69,0x6e), // penguin
c(0x74,0x75,0x72,0x74,0x6c,0x65), // turtle
c(0x73,0x6e,0x61,0x69,0x6c), // snail
c(0x67,0x68,0x6f,0x73,0x74), // ghost
c(0x61,0x78,0x6f,0x6c,0x6f,0x74,0x6c), // axolotl
c(0x63,0x61,0x70,0x79,0x62,0x61,0x72,0x61), // capybara
c(0x63,0x61,0x63,0x74,0x75,0x73), // cactus
c(0x72,0x6f,0x62,0x6f,0x74), // robot
c(0x72,0x61,0x62,0x62,0x69,0x74), // rabbit
c(0x6d,0x75,0x73,0x68,0x72,0x6f,0x6f,0x6d), // mushroom
c(0x63,0x68,0x6f,0x6e,0x6b), // chonk
]
const EYES = ['·', '✦', '×', '◉', '@', '°']
const HATS = ['none','crown','tophat','propeller','halo','wizard','beanie','tinyduck']
const RARITIES = ['common','uncommon','rare','epic','legendary']
const RARITY_WEIGHTS = { common:60, uncommon:25, rare:10, epic:4, legendary:1 }
const RARITY_FLOOR = { common:5, uncommon:15, rare:25, epic:35, legendary:50 }
const RARITY_STARS = { common:'★', uncommon:'★★', rare:'★★★', epic:'★★★★', legendary:'★★★★★' }
const STAT_NAMES = ['DEBUGGING','PATIENCE','CHAOS','WISDOM','SNARK']
const SALT = 'friend-2026-401'
// ── Sprite Art (from buddy/sprites.ts) ──
const [duck,goose,blob,cat,dragon,octopus,owl,penguin,turtle,snail,ghost,axolotl,capybara,cactus,robot,rabbit,mushroom,chonk] = SPECIES
const BODIES = {
[duck]: [[' ',' __ ',' <({E} )___ ',' ( ._> ',' `--´ ']],
[goose]: [[' ',' ({E}> ',' || ',' _(__)_ ',' ^^^^ ']],
[blob]: [[' ',' .----. ',' ( {E} {E} ) ',' ( ) ',' `----´ ']],
[cat]: [[' ',' /\\_/\\ ',' ( {E} {E}) ',' ( ω ) ',' (")_(") ']],
[dragon]: [[' ',' /^\\ /^\\ ',' < {E} {E} > ',' ( ~~ ) ',' \\_/ \\_/ ']],
[octopus]: [[' ',' ~(______)~ ',' ~({E} .. {E})~ ',' ( .--. ) ',' (_/ \\_) ']],
[owl]: [[' ',' n______n ',' ( {E} {E} ) ',' ( oo ) ',' `------´ ']],
[penguin]: [[' ',' ____ ',' /{E} {E}\\ ',' ( `----´ ) ',' ( )( ) ']],
[turtle]: [[' ',' .---. ',' /{E}____{E}\\ ',' |======| ',' ( )( ) ']],
[snail]: [[' ',' ({E}) ',' /| ___ ',' / | ( ) ',' ~~~~~~´ ']],
[ghost]: [[' ',' .----. ',' ( {E} {E} ) ',' ( ) ',' /_/\\_/\\_ ']],
[axolotl]: [[' ','}~(______)~{',' }({E} .. {E}){ ',' ( .--. ) ',' (_/ \\_) ']],
[capybara]: [[' ',' .----. ',' ({E} {E}) ',' =( .. )= ',' (")__(") ']],
[cactus]: [[' ',' .-o-OO-o-. ','(__________)',' |{E} {E}| ',' |____| ']],
[robot]: [[' ',' .[||]. ',' [ {E} {E} ] ',' [ ==== ] ',' [_/ \\_] ']],
[rabbit]: [[' ',' /\\ /\\ ',' ( {E} {E} ) ',' ( .. ) ',' `------´ ']],
[mushroom]: [[' ',' .^^^^^^. ',' (________) ',' |{E} {E}| ',' |____| ']],
[chonk]: [[' ',' /\\ /\\ ',' ( {E} {E} ) ',' ( .. ) ',' `------´ ']],
}
const HAT_LINES = {
none:'', crown:' \\^^^/ ', tophat:' [___] ', propeller:' -+- ',
halo:' ( ) ', wizard:' /^\\ ', beanie:' (___) ', tinyduck:' ,> ',
}
// ── PRNG (Mulberry32 — from buddy/companion.ts) ──
function mulberry32(seed) {
let a = seed >>> 0
return function() {
a |= 0; a = (a + 0x6d2b79f5) | 0
let t = Math.imul(a ^ (a >>> 15), 1 | a)
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
function hashString(s) {
let h = 2166136261
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i); h = Math.imul(h, 16777619)
}
return h >>> 0
}
function pick(rng, arr) { return arr[Math.floor(rng() * arr.length)] }
function rollRarity(rng) {
const total = Object.values(RARITY_WEIGHTS).reduce((a,b) => a+b, 0)
let roll = rng() * total
for (const r of RARITIES) { roll -= RARITY_WEIGHTS[r]; if (roll < 0) return r }
return 'common'
}
function rollStats(rng, rarity) {
const floor = RARITY_FLOOR[rarity]
const peak = pick(rng, STAT_NAMES)
let dump = pick(rng, STAT_NAMES)
while (dump === peak) dump = pick(rng, STAT_NAMES)
const stats = {}
for (const name of STAT_NAMES) {
if (name === peak) stats[name] = Math.min(100, floor + 50 + Math.floor(rng() * 30))
else if (name === dump) stats[name] = Math.max(1, floor - 10 + Math.floor(rng() * 15))
else stats[name] = floor + Math.floor(rng() * 40)
}
return stats
}
function roll(userId) {
const rng = mulberry32(hashString(userId + SALT))
const rarity = rollRarity(rng)
return {
rarity, species: pick(rng, SPECIES), eye: pick(rng, EYES),
hat: rarity === 'common' ? 'none' : pick(rng, HATS),
shiny: rng() < 0.01, stats: rollStats(rng, rarity),
}
}
// ── Sprite Renderer ──
function renderSprite(bones) {
const frames = BODIES[bones.species]
const lines = [...frames[0]].map(l => l.replaceAll('{E}', bones.eye))
if (bones.hat !== 'none' && !lines[0].trim()) lines[0] = HAT_LINES[bones.hat]
if (!lines[0].trim()) lines.shift()
return lines
}
// ── Config ──
function getConfig() {
const paths = [
join(homedir(), '.claude.json'),
join(homedir(), '.claude', '.config.json'),
join(homedir(), '.claude', 'config.json'),
]
for (const p of paths) {
if (existsSync(p)) return { path: p, data: JSON.parse(readFileSync(p, 'utf8')) }
}
return { path: paths[0], data: {} }
}
// ── Soul Generator (names from hash) ──
const NAMES = [
'Pixel','Huskle','Spark','Nibble','Glitch','Widget','Ember','Frost',
'Dash','Blip','Mochi','Ziggy','Chip','Twig','Fizz','Boop',
'Noodle','Pebble','Quirk','Sage',
]
const TRAITS = [
'who leaves verbose debugging notes and believes every error deserves a eulogy',
'who optimizes everything, including their own breakfast routine',
'who collects edge cases like rare stamps',
'who speaks exclusively in function signatures when excited',
'who insists on testing in production "just to feel alive"',
'who refactors your refactors while you sleep',
'who writes commit messages as haiku',
'who maintains a personal changelog of their own life events',
]
function generateSoul(rng, species) {
const name = pick(rng, NAMES)
const trait = pick(rng, TRAITS)
return { name, personality: `A methodical ${species} ${trait}.` }
}
// ── Card Renderer ──
const DIM = '\x1b[2m'
const RESET = '\x1b[0m'
const BOLD = '\x1b[1m'
const WHITE = '\x1b[37m'
const GREEN = '\x1b[32m'
const YELLOW = '\x1b[33m'
const CYAN = '\x1b[36m'
const MAGENTA = '\x1b[35m'
const RED = '\x1b[31m'
const RARITY_COLOR = { common: DIM, uncommon: GREEN, rare: CYAN, epic: MAGENTA, legendary: YELLOW }
function statBar(value, width = 15) {
const filled = Math.round(value / 100 * width)
return '█'.repeat(filled) + DIM + '░'.repeat(width - filled) + RESET
}
function renderCard(bones, soul) {
const W = 44
const rc = RARITY_COLOR[bones.rarity]
const stars = RARITY_STARS[bones.rarity]
const sprite = renderSprite(bones)
const header = `${rc}${stars} ${bones.rarity.toUpperCase()}${RESET}`
const speciesLabel = `${bones.species.toUpperCase()}${bones.shiny ? ' ✨' : ''}`
const lines = []
lines.push('')
lines.push(` ${DIM}${'─'.repeat(W)}${RESET}`)
lines.push(` ${DIM}${RESET} ${header}${' '.repeat(Math.max(1, W - 4 - stars.length - bones.rarity.length - 1))}${speciesLabel} ${DIM}${RESET}`)
lines.push(` ${DIM}${' '.repeat(W)}${RESET}`)
for (const sl of sprite) {
const pad = Math.max(0, W - sl.length)
lines.push(` ${DIM}${RESET}${' '.repeat(Math.floor(pad/2))}${WHITE}${sl}${RESET}${' '.repeat(Math.ceil(pad/2))}${DIM}${RESET}`)
}
lines.push(` ${DIM}${' '.repeat(W)}${RESET}`)
lines.push(` ${DIM}${RESET} ${BOLD}${soul.name}${RESET}${' '.repeat(W - soul.name.length - 2)}${DIM}${RESET}`)
lines.push(` ${DIM}${' '.repeat(W)}${RESET}`)
// Personality (word-wrap)
const maxW = W - 4
const words = soul.personality.split(' ')
let cur = ''
const pLines = []
for (const w of words) {
if (cur.length + w.length + 1 > maxW) { pLines.push(cur); cur = w }
else cur = cur ? cur + ' ' + w : w
}
if (cur) pLines.push(cur)
for (const pl of pLines) {
lines.push(` ${DIM}${RESET} ${DIM}"${pl}"${RESET}${' '.repeat(Math.max(0, W - pl.length - 4))}${DIM}${RESET}`)
}
lines.push(` ${DIM}${' '.repeat(W)}${RESET}`)
// Stats
for (const stat of STAT_NAMES) {
const val = bones.stats[stat]
const label = stat.padEnd(10)
const bar = statBar(val)
const num = String(val).padStart(3)
lines.push(` ${DIM}${RESET} ${BOLD}${label}${RESET} ${bar} ${num} ${DIM}${RESET}`)
}
lines.push(` ${DIM}${' '.repeat(W)}${RESET}`)
lines.push(` ${DIM}${'─'.repeat(W)}${RESET}`)
// Footer
lines.push('')
lines.push(` ${DIM}${soul.name} is here · it'll chime in as you code${RESET}`)
lines.push(` ${DIM}your buddy won't count toward your usage${RESET}`)
lines.push(` ${DIM}say its name to get its take · /buddy pet · /buddy off${RESET}`)
lines.push('')
lines.push(` ${DIM}press any key${RESET}`)
lines.push('')
return lines.join('\n')
}
// ── Main ──
const { path: cfgPath, data: config } = getConfig()
const userId = config.oauthAccount?.accountUuid ?? config.userID ?? 'anon'
const bones = roll(userId)
// Generate or load soul
let soul
if (config.companion?.name) {
soul = { name: config.companion.name, personality: config.companion.personality }
} else {
const soulRng = mulberry32(hashString(userId + SALT + 'soul'))
soul = generateSoul(soulRng, bones.species)
// Save to config
config.companion = { ...soul, hatchedAt: Date.now() }
config.companionMuted = false
writeFileSync(cfgPath, JSON.stringify(config, null, 2))
}
console.log(renderCard(bones, soul))