支持buddy命令
This commit is contained in:
parent
03cff1b749
commit
c57ad656ad
@ -115,11 +115,8 @@ const forkCmd = feature('FORK_SUBAGENT')
|
|||||||
require('./commands/fork/index.js') as typeof import('./commands/fork/index.js')
|
require('./commands/fork/index.js') as typeof import('./commands/fork/index.js')
|
||||||
).default
|
).default
|
||||||
: null
|
: null
|
||||||
const buddy = feature('BUDDY')
|
// buddy loaded directly (not feature-gated) for this build
|
||||||
? (
|
import buddy from './commands/buddy/index.js'
|
||||||
require('./commands/buddy/index.js') as typeof import('./commands/buddy/index.js')
|
|
||||||
).default
|
|
||||||
: null
|
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||||
import thinkback from './commands/thinkback/index.js'
|
import thinkback from './commands/thinkback/index.js'
|
||||||
import thinkbackPlay from './commands/thinkback-play/index.js'
|
import thinkbackPlay from './commands/thinkback-play/index.js'
|
||||||
@ -319,7 +316,7 @@ const COMMANDS = memoize((): Command[] => [
|
|||||||
vim,
|
vim,
|
||||||
...(webCmd ? [webCmd] : []),
|
...(webCmd ? [webCmd] : []),
|
||||||
...(forkCmd ? [forkCmd] : []),
|
...(forkCmd ? [forkCmd] : []),
|
||||||
...(buddy ? [buddy] : []),
|
buddy,
|
||||||
...(proactive ? [proactive] : []),
|
...(proactive ? [proactive] : []),
|
||||||
...(briefCommand ? [briefCommand] : []),
|
...(briefCommand ? [briefCommand] : []),
|
||||||
...(assistantCommand ? [assistantCommand] : []),
|
...(assistantCommand ? [assistantCommand] : []),
|
||||||
|
|||||||
254
src/commands/buddy/buddy.ts
Normal file
254
src/commands/buddy/buddy.ts
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
import {
|
||||||
|
getCompanion,
|
||||||
|
roll,
|
||||||
|
type Roll,
|
||||||
|
companionUserId,
|
||||||
|
} from '../../buddy/companion.js'
|
||||||
|
import {
|
||||||
|
type StoredCompanion,
|
||||||
|
RARITY_STARS,
|
||||||
|
STAT_NAMES,
|
||||||
|
SPECIES,
|
||||||
|
} from '../../buddy/types.js'
|
||||||
|
import { renderSprite } from '../../buddy/sprites.js'
|
||||||
|
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
|
||||||
|
import type { LocalCommandCall } 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStats(stats: Record<string, number>): string {
|
||||||
|
const lines = STAT_NAMES.map(name => {
|
||||||
|
const val = stats[name] ?? 0
|
||||||
|
const filled = Math.round(val / 5)
|
||||||
|
const bar = '█'.repeat(filled) + '░'.repeat(20 - filled)
|
||||||
|
return ` ${name.padEnd(10)} ${bar} ${val}`
|
||||||
|
})
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function companionInfoText(roll: Roll): string {
|
||||||
|
const { bones } = roll
|
||||||
|
const sprite = renderSprite(bones, 0)
|
||||||
|
const stars = RARITY_STARS[bones.rarity]
|
||||||
|
const name = SPECIES_NAMES[bones.species] ?? 'Buddy'
|
||||||
|
const shiny = bones.shiny ? ' ✨ Shiny!' : ''
|
||||||
|
|
||||||
|
return [
|
||||||
|
sprite.join('\n'),
|
||||||
|
'',
|
||||||
|
` ${name} the ${speciesLabel(bones.species)}${shiny}`,
|
||||||
|
` Rarity: ${stars} (${bones.rarity})`,
|
||||||
|
` Eye: ${bones.eye} Hat: ${bones.hat}`,
|
||||||
|
'',
|
||||||
|
' Stats:',
|
||||||
|
renderStats(bones.stats),
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const call: LocalCommandCall = async (args, _context) => {
|
||||||
|
const sub = args.trim().toLowerCase()
|
||||||
|
const config = getGlobalConfig()
|
||||||
|
|
||||||
|
// /buddy — show current companion or hint to hatch
|
||||||
|
if (sub === '') {
|
||||||
|
const companion = getCompanion()
|
||||||
|
if (!companion) {
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
value:
|
||||||
|
"You don't have a companion yet! Use /buddy hatch to get one.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const stars = RARITY_STARS[companion.rarity]
|
||||||
|
const sprite = renderSprite(companion, 0)
|
||||||
|
const shiny = companion.shiny ? ' ✨ Shiny!' : ''
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
sprite.join('\n'),
|
||||||
|
'',
|
||||||
|
` ${companion.name} the ${speciesLabel(companion.species)}${shiny}`,
|
||||||
|
` Rarity: ${stars} (${companion.rarity})`,
|
||||||
|
` Eye: ${companion.eye} Hat: ${companion.hat}`,
|
||||||
|
companion.personality ? `\n "${companion.personality}"` : '',
|
||||||
|
'',
|
||||||
|
' Stats:',
|
||||||
|
renderStats(companion.stats),
|
||||||
|
'',
|
||||||
|
' Commands: /buddy pet /buddy mute /buddy unmute /buddy hatch /buddy rehatch',
|
||||||
|
]
|
||||||
|
return { type: 'text', value: lines.join('\n') }
|
||||||
|
}
|
||||||
|
|
||||||
|
// /buddy hatch — create a new companion
|
||||||
|
if (sub === 'hatch') {
|
||||||
|
if (config.companion) {
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
value: `You already have a companion! Use /buddy to see it.\n(Tip: /buddy hatch again will re-roll a new one.)`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = roll(companionUserId())
|
||||||
|
const name = SPECIES_NAMES[r.bones.species] ?? 'Buddy'
|
||||||
|
const personality =
|
||||||
|
SPECIES_PERSONALITY[r.bones.species] ?? 'Mysterious and code-savvy.'
|
||||||
|
|
||||||
|
const stored: StoredCompanion = {
|
||||||
|
name,
|
||||||
|
personality,
|
||||||
|
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 ? ' ✨ Shiny!' : ''
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
' 🎉 A wild companion appeared!',
|
||||||
|
'',
|
||||||
|
sprite.join('\n'),
|
||||||
|
'',
|
||||||
|
` ${name} the ${speciesLabel(r.bones.species)}${shiny}`,
|
||||||
|
` Rarity: ${stars} (${r.bones.rarity})`,
|
||||||
|
` "${personality}"`,
|
||||||
|
'',
|
||||||
|
' Your companion will now appear beside your input box!',
|
||||||
|
]
|
||||||
|
return { type: 'text', value: lines.join('\n') }
|
||||||
|
}
|
||||||
|
|
||||||
|
// /buddy pet — trigger heart animation
|
||||||
|
if (sub === 'pet') {
|
||||||
|
const companion = getCompanion()
|
||||||
|
if (!companion) {
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
value:
|
||||||
|
"You don't have a companion yet! Use /buddy hatch to get one.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import setAppState dynamically to update companionPetAt
|
||||||
|
try {
|
||||||
|
const { setAppState } = await import('../../state/AppStateStore.js')
|
||||||
|
setAppState(prev => ({
|
||||||
|
...prev,
|
||||||
|
companionPetAt: Date.now(),
|
||||||
|
}))
|
||||||
|
} catch {
|
||||||
|
// If AppState is not available (non-interactive), just show text
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
value: ` ${renderSprite(companion, 0).join('\n')}\n\n ${companion.name} purrs happily! ♥`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /buddy mute
|
||||||
|
if (sub === 'mute') {
|
||||||
|
if (config.companionMuted) {
|
||||||
|
return { type: 'text', value: ' Companion is already muted.' }
|
||||||
|
}
|
||||||
|
saveGlobalConfig(cfg => ({ ...cfg, companionMuted: true }))
|
||||||
|
return { type: 'text', value: ' Companion muted. It will hide quietly. Use /buddy unmute to bring it back.' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// /buddy unmute
|
||||||
|
if (sub === 'unmute') {
|
||||||
|
if (!config.companionMuted) {
|
||||||
|
return { type: 'text', value: ' Companion is not muted.' }
|
||||||
|
}
|
||||||
|
saveGlobalConfig(cfg => ({ ...cfg, companionMuted: false }))
|
||||||
|
return { type: 'text', value: ' Companion unmuted! Welcome back.' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// /buddy rehatch — re-roll a new companion (replaces existing)
|
||||||
|
if (sub === 'rehatch') {
|
||||||
|
const r = roll(companionUserId())
|
||||||
|
const name = SPECIES_NAMES[r.bones.species] ?? 'Buddy'
|
||||||
|
const personality =
|
||||||
|
SPECIES_PERSONALITY[r.bones.species] ?? 'Mysterious and code-savvy.'
|
||||||
|
|
||||||
|
const stored: StoredCompanion = {
|
||||||
|
name,
|
||||||
|
personality,
|
||||||
|
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 ? ' ✨ Shiny!' : ''
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
' 🎉 A new companion appeared!',
|
||||||
|
'',
|
||||||
|
sprite.join('\n'),
|
||||||
|
'',
|
||||||
|
` ${name} the ${speciesLabel(r.bones.species)}${shiny}`,
|
||||||
|
` Rarity: ${stars} (${r.bones.rarity})`,
|
||||||
|
` "${personality}"`,
|
||||||
|
'',
|
||||||
|
' Your old companion has been replaced!',
|
||||||
|
]
|
||||||
|
return { type: 'text', value: lines.join('\n') }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown subcommand
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
value:
|
||||||
|
' Unknown command: /buddy ' +
|
||||||
|
sub +
|
||||||
|
'\n Commands: /buddy (info) /buddy hatch /buddy rehatch /buddy pet /buddy mute /buddy unmute',
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,11 @@
|
|||||||
// Auto-generated stub — replace with real implementation
|
import type { Command } from '../../commands.js'
|
||||||
const _default: Record<string, unknown> = {};
|
|
||||||
export default _default;
|
const buddy = {
|
||||||
|
type: 'local',
|
||||||
|
name: 'buddy',
|
||||||
|
description: 'View and manage your companion buddy',
|
||||||
|
supportsNonInteractive: false,
|
||||||
|
load: () => import('./buddy.js'),
|
||||||
|
} satisfies Command
|
||||||
|
|
||||||
|
export default buddy
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
// Runtime polyfill for bun:bundle (build-time macros)
|
// Runtime polyfill for bun:bundle (build-time macros)
|
||||||
const feature = (_name: string) => false;
|
const feature = (name: string) => name === "BUDDY";
|
||||||
if (typeof globalThis.MACRO === "undefined") {
|
if (typeof globalThis.MACRO === "undefined") {
|
||||||
(globalThis as any).MACRO = {
|
(globalThis as any).MACRO = {
|
||||||
VERSION: "2.1.888",
|
VERSION: "2.1.888",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user