File size: 5,629 Bytes
97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 97289d7 064bfd6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | import React from 'react'
import {
getCompanion,
rollWithSeed,
generateSeed,
} from '../../buddy/companion.js'
import { type StoredCompanion, RARITY_STARS } from '../../buddy/types.js'
import { renderSprite } from '../../buddy/sprites.js'
import { CompanionCard } from '../../buddy/CompanionCard.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { triggerCompanionReaction } from '../../buddy/companionReact.js'
import type { ToolUseContext } from '../../Tool.js'
import type {
LocalJSXCommandContext,
LocalJSXCommandOnDone,
} from '../../types/command.js'
// Species β default name fragments for hatch (no API needed)
const SPECIES_NAMES: Record<string, string> = {
duck: 'Waddles',
goose: 'Goosberry',
blob: 'Gooey',
cat: 'Whiskers',
dragon: 'Ember',
octopus: 'Inky',
owl: 'Hoots',
penguin: 'Waddleford',
turtle: 'Shelly',
snail: 'Trailblazer',
ghost: 'Casper',
axolotl: 'Axie',
capybara: 'Chill',
cactus: 'Spike',
robot: 'Byte',
rabbit: 'Flops',
mushroom: 'Spore',
chonk: 'Chonk',
}
const SPECIES_PERSONALITY: Record<string, string> = {
duck: 'Quirky and easily amused. Leaves rubber duck debugging tips everywhere.',
goose: 'Assertive and honks at bad code. Takes no prisoners in code reviews.',
blob: 'Adaptable and goes with the flow. Sometimes splits into two when confused.',
cat: 'Independent and judgmental. Watches you type with mild disdain.',
dragon:
'Fiery and passionate about architecture. Hoards good variable names.',
octopus:
'Multitasker extraordinaire. Wraps tentacles around every problem at once.',
owl: 'Wise but verbose. Always says "let me think about that" for exactly 3 seconds.',
penguin: 'Cool under pressure. Slides gracefully through merge conflicts.',
turtle: 'Patient and thorough. Believes slow and steady wins the deploy.',
snail: 'Methodical and leaves a trail of useful comments. Never rushes.',
ghost:
'Ethereal and appears at the worst possible moments with spooky insights.',
axolotl: 'Regenerative and cheerful. Recovers from any bug with a smile.',
capybara: 'Zen master. Remains calm while everything around is on fire.',
cactus:
'Prickly on the outside but full of good intentions. Thrives on neglect.',
robot: 'Efficient and literal. Processes feedback in binary.',
rabbit: 'Energetic and hops between tasks. Finishes before you start.',
mushroom: 'Quietly insightful. Grows on you over time.',
chonk:
'Big, warm, and takes up the whole couch. Prioritizes comfort over elegance.',
}
function speciesLabel(species: string): string {
return species.charAt(0).toUpperCase() + species.slice(1)
}
export async function call(
onDone: LocalJSXCommandOnDone,
context: ToolUseContext & LocalJSXCommandContext,
args: string,
): Promise<React.ReactNode> {
const sub = args?.trim().toLowerCase() ?? ''
const setState = context.setAppState
// ββ /buddy off β mute companion ββ
if (sub === 'off') {
saveGlobalConfig(cfg => ({ ...cfg, companionMuted: true }))
onDone('companion muted', { display: 'system' })
return null
}
// ββ /buddy on β unmute companion ββ
if (sub === 'on') {
saveGlobalConfig(cfg => ({ ...cfg, companionMuted: false }))
onDone('companion unmuted', { display: 'system' })
return null
}
// ββ /buddy pet β trigger heart animation + auto unmute ββ
if (sub === 'pet') {
const companion = getCompanion()
if (!companion) {
onDone('no companion yet \u00b7 run /buddy first', { display: 'system' })
return null
}
// Auto-unmute on pet + trigger heart animation
saveGlobalConfig(cfg => ({ ...cfg, companionMuted: false }))
setState?.(prev => ({ ...prev, companionPetAt: Date.now() }))
// Trigger a post-pet reaction
triggerCompanionReaction(context.messages ?? [], reaction =>
setState?.(prev =>
prev.companionReaction === reaction
? prev
: { ...prev, companionReaction: reaction },
),
)
onDone(`petted ${companion.name}`, { display: 'system' })
return null
}
// ββ /buddy (no args) β show existing or hatch ββ
const companion = getCompanion()
// Auto-unmute when viewing
if (companion && getGlobalConfig().companionMuted) {
saveGlobalConfig(cfg => ({ ...cfg, companionMuted: false }))
}
if (companion) {
// Return JSX card β matches official vc8 component
const lastReaction = context.getAppState?.()?.companionReaction
return React.createElement(CompanionCard, {
companion,
lastReaction,
onDone,
})
}
// ββ No companion β hatch ββ
const seed = generateSeed()
const r = rollWithSeed(seed)
const name = SPECIES_NAMES[r.bones.species] ?? 'Buddy'
const personality =
SPECIES_PERSONALITY[r.bones.species] ?? 'Mysterious and code-savvy.'
const stored: StoredCompanion = {
name,
personality,
seed,
hatchedAt: Date.now(),
}
saveGlobalConfig(cfg => ({ ...cfg, companion: stored }))
const stars = RARITY_STARS[r.bones.rarity]
const sprite = renderSprite(r.bones, 0)
const shiny = r.bones.shiny ? ' \u2728 Shiny!' : ''
const lines = [
'A wild companion appeared!',
'',
...sprite,
'',
`${name} the ${speciesLabel(r.bones.species)}${shiny}`,
`Rarity: ${stars} (${r.bones.rarity})`,
`"${personality}"`,
'',
'Your companion will now appear beside your input box!',
'Say its name to get its take \u00b7 /buddy pet \u00b7 /buddy off',
]
onDone(lines.join('\n'), { display: 'system' })
return null
}
|