diff --git a/src/commands.ts b/src/commands.ts index f0fc6675a..98abcc23b 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -155,6 +155,11 @@ const buddy = feature('BUDDY') require('./commands/buddy/index.js') as typeof import('./commands/buddy/index.js') ).default : null +const pokemonBattle = feature('BUDDY') + ? ( + require('./commands/pokemon-battle/index.js') as typeof import('./commands/pokemon-battle/index.js') + ).default + : null const poor = feature('POOR') ? ( require('./commands/poor/index.js') as typeof import('./commands/poor/index.js') @@ -364,6 +369,7 @@ const COMMANDS = memoize((): Command[] => [ ...(webCmd ? [webCmd] : []), ...(forkCmd ? [forkCmd] : []), ...(buddy ? [buddy] : []), + ...(pokemonBattle ? [pokemonBattle] : []), ...(poor ? [poor] : []), ...(proactive ? [proactive] : []), ...(monitorCmd ? [monitorCmd] : []), diff --git a/src/commands/buddy/BuddyPanel.tsx b/src/commands/buddy/BuddyPanel.tsx index 962fdeb6a..3438215bf 100644 --- a/src/commands/buddy/BuddyPanel.tsx +++ b/src/commands/buddy/BuddyPanel.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useState, useRef } from 'react'; +import { useState } from 'react'; import { Box, Text, Pane, Tab, Tabs, useInput, type Color } from '@anthropic/ink'; import { useSetAppState } from '../../state/AppState.js'; import { useKeybinding } from '../../keybindings/useKeybinding.js'; @@ -21,8 +21,6 @@ import { getXpProgress } from '@claude-code-best/pokemon'; import { getGenderSymbol } from '@claude-code-best/pokemon'; import { StatBar, SpriteAnimator, getFallbackSprite, loadSprite } from '@claude-code-best/pokemon'; -import { BattleFlow, loadBuddyData } from '@claude-code-best/pokemon'; -import type { BattleFlowHandle } from '@claude-code-best/pokemon'; import type { LocalJSXCommandOnDone } from '../../types/command.js'; const CYAN: Color = 'ansi:cyan'; @@ -93,13 +91,6 @@ export function BuddyPanel({ buddyData, spriteLines, onClose }: BuddyPanelProps) onClose={() => onClose('buddy panel closed')} /> , - - - , , @@ -622,43 +613,6 @@ function DexTab({ ); } -// ─── Battle Tab ────────────────────────────────────────── - -function BattleTab({ - buddyData, - isActive, - onUpdate, -}: { - buddyData: BuddyData; - isActive: boolean; - onUpdate: (data: BuddyData) => void; -}) { - const [battleKey, setBattleKey] = useState(0); - const inputRef = useRef(null); - - // Handle input here (in main app's Ink context) and forward to BattleFlow via ref - useInput((input, key) => { - if (!isActive) return; - inputRef.current?.handleInput(input, key); - }); - - const handleClose = async () => { - const updated = await loadBuddyData(); - onUpdate(updated); - setBattleKey(k => k + 1); - }; - - return ( - - ); -} - // ─── Egg Tab ────────────────────────────────────────── function EggTab({ buddyData }: { buddyData: BuddyData }) { diff --git a/src/commands/pokemon-battle/index.ts b/src/commands/pokemon-battle/index.ts new file mode 100644 index 000000000..d2acd7662 --- /dev/null +++ b/src/commands/pokemon-battle/index.ts @@ -0,0 +1,15 @@ +import type { Command } from '../../commands.js' +import { isBuddyLive } from '../../buddy/useBuddyNotification.js' + +const pokemonBattle = { + type: 'local-jsx', + name: 'pokemon-battle', + description: 'Start a Pokémon battle', + immediate: true, + get isHidden() { + return !isBuddyLive() + }, + load: () => import('./pokemon-battle.js'), +} satisfies Command + +export default pokemonBattle diff --git a/src/commands/pokemon-battle/pokemon-battle.ts b/src/commands/pokemon-battle/pokemon-battle.ts new file mode 100644 index 000000000..d4cddbadc --- /dev/null +++ b/src/commands/pokemon-battle/pokemon-battle.ts @@ -0,0 +1,71 @@ +import React, { useState, useRef } from 'react' +import { useInput } from '@anthropic/ink' +import { + loadBuddyData, + saveBuddyData, + getActiveCreature, + BattleFlow, + type BuddyData, + type BattleFlowHandle, +} from '@claude-code-best/pokemon' +import type { ToolUseContext } from '../../Tool.js' +import type { + LocalJSXCommandContext, + LocalJSXCommandOnDone, +} from '../../types/command.js' + +async function getOrInitBuddyData(): Promise { + let data = await loadBuddyData() + if (!getActiveCreature(data)) { + data = await loadBuddyData() + } + return data +} + +export async function call( + onDone: LocalJSXCommandOnDone, + _context: ToolUseContext & LocalJSXCommandContext, + _args: string, +): Promise { + const data = await getOrInitBuddyData() + + if (!getActiveCreature(data)) { + onDone('No companion yet · run /buddy first', { display: 'system' }) + return null + } + + return React.createElement(BattlePanel, { + buddyData: data, + onClose: () => { + onDone('battle closed', { display: 'system' }) + }, + }) +} + +function BattlePanel({ + buddyData, + onClose, +}: { + buddyData: BuddyData + onClose: () => void +}) { + const [battleKey, setBattleKey] = useState(0) + const inputRef = useRef(null) + + useInput((input, key) => { + inputRef.current?.handleInput(input, key) + }) + + const handleClose = async () => { + const updated = await loadBuddyData() + setBattleKey(k => k + 1) + } + + return React.createElement(BattleFlow, { + key: battleKey, + buddyData, + onClose, + isActive: true, + inputRef, + }) +}