@hhoangphuoc/escape-room-cli
Version:
A CLI for playing AI-generated escape room games. Install globally with: npm install -g @hhoangphuoc/escape-room-cli
335 lines (334 loc) • 16.3 kB
JavaScript
// escape-room-cli/source/handlers/commandProcessor.ts
import { handleHelpCommand, handleLookCommand, handleInspectCommand, handleGuessCommand, handlePasswordCommand, handleHintCommand, handleNewGameCommand, handleLeaderboardCommand,
// handleLeaderboardMeCommand,
handleLogoutCommand, handleLoginCommand, handleEndSessionCommand, handleNaturalLanguageCommand, handleInstructionsCommand, handleUsageCommand, handleCostCommand,
// Simplified cost monitoring - removed handlePricingCommand, handleCompareCommand, handleEstimateCommand
} from './commandHandlers.js';
import { displayResponse } from '../utils/responseDisplay.js';
// const REASONING_HOTKEY_MAX = 9;
// let reasoningHotkeyCursor = 1;
// const getNextReasoningHotkey = () => {
// const current = reasoningHotkeyCursor;
// reasoningHotkeyCursor = reasoningHotkeyCursor % REASONING_HOTKEY_MAX + 1;
// return current;
// };
const prepareHistoryItems = (items) => {
return items.map((item) => {
if (item.responseType === 'ai' && item.data && (item.data.reasoning?.summary || (item.data.reasoning?.steps && item.data.reasoning.steps.length > 0))) {
// const hotkey = getNextReasoningHotkey();
return {
...item,
// reasoningHotkey: hotkey,
data: {
...item.data,
// reasoningExpanded: false,
},
};
}
return item;
});
};
// import { getApiUrl } from './apiConfig.js';
// Centralized processor for all commands
export const handleCommand = async (props) => {
const { command, auth, game, setHistory, setIsLoading, setLoadingMessage, setShowHistory, setShowModelSelector, setShowInstructions, setShowLeaderboard, setShowCostDashboard, setShowCostMonitor, selectedModel, conversation } = props;
const THINKING_MESSAGE = [
`🤔 Thinking with ${selectedModel.label}...`,
`🍲 Crafting solutions in ${selectedModel.label} ...`,
`✨ Sparkling your thoughts in ${selectedModel.label}...`,
`🔍 Investigating the puzzles with ${selectedModel.label}...`,
`🔥 My ${selectedModel.label} thinking is on fire...`,
];
if (command.startsWith('/')) {
const parts = command.trim().split(/\s+/);
const cmd = parts[0]?.toLowerCase();
const userContext = {
userId: auth.userId,
sessionToken: auth.sessionToken,
userName: auth.userName,
cliApiKey: auth.apiKey,
hasAICapability: auth.apiKey ? true : false,
};
const gameContext = {
currentGameId: game.gameId,
currentRoomName: game.roomName,
currentRoomBackground: game.roomBackground,
currentGameMode: game.gameMode,
totalRooms: game.totalRooms,
unlockedObjects: game.unlockedObjects,
currentRoomObjects: game.currentRoomObjects,
};
let response;
switch (cmd) {
case '/help':
response = handleHelpCommand(userContext, gameContext);
break;
case '/look':
response = await handleLookCommand(userContext);
if (response.success && response.roomData)
game.setGameFromLook(response.roomData);
break;
case '/inspect':
const objectName = parts.slice(1).join(' ');
response = await handleInspectCommand(objectName, userContext);
break;
case '/guess':
const guessObjectName = parts.slice(1, -1).join(' ');
const answer = parts[parts.length - 1];
response = await handleGuessCommand(guessObjectName, answer, userContext);
if (response.success && response.objectData?.unlocked) {
game.unlockObject(response.objectData.name);
}
break;
case '/password':
const password = parts.slice(1).join(' ');
response = await handlePasswordCommand(password, userContext);
if (response.success && response.gameResult?.gameCompleted) {
game.setGameCompleted();
// Persist conversation to Firebase when game is completed
if (conversation && conversation.currentConversation && conversation.persistConversation && game.gameId) {
try {
const persisted = await conversation.persistConversation(game.gameId);
if (persisted) {
}
else {
console.warn('Failed to save conversation history');
}
}
catch (error) {
console.error('Failed to persist conversation on game completion:', error);
}
}
}
else if (response.success && response.gameResult?.escaped) {
game.fetchGameState();
}
break;
case '/hint':
response = await handleHintCommand(userContext);
break;
case '/newgame':
const mode = parts[1]?.toLowerCase() || 'single-room';
setIsLoading(true);
setLoadingMessage(` 🍲 Cooking up an AI Escape Room... Stay tuned!`);
response = await handleNewGameCommand(mode, userContext);
if (response.success && response.gameData) {
game.setGame(response.gameData);
// Initialize conversation for new game
if (conversation && conversation.createNewConversation && response.gameData.id) {
try {
const sessionId = `session_${Date.now()}`;
conversation.createNewConversation(String(response.gameData.id), sessionId, response.gameData.mode || 'unknown');
// Add initial system message for game start
setTimeout(async () => {
if (conversation.addEntry) {
await conversation.addEntry({
type: 'system_message',
content: `New game started: ${response.gameData.name} (${response.gameData.mode})`,
metadata: {
requestType: 'system',
gameId: String(response.gameData.id),
roomId: response.gameData.name,
},
});
}
}, 100); // Small delay to ensure conversation is fully initialized
}
catch (error) {
console.error('Failed to initialize conversation for new game:', error);
}
}
}
setIsLoading(false);
break;
case '/leaderboard':
const leaderboardMode = parts[1]?.toLowerCase() || 'time';
response = await handleLeaderboardCommand(userContext, leaderboardMode);
if (response.success && response.leaderboardData) {
setShowLeaderboard(response.leaderboardData);
return;
}
break;
// case '/leaderboard/me':
// response = await handleLeaderboardMeCommand(userContext);
// if (response.success && response.leaderboardData) {
// setShowLeaderboard(response.leaderboardData);
// return;
// }
// break;
case '/instructions':
response = handleInstructionsCommand(userContext, gameContext);
if (response.success) {
setShowInstructions(true);
return;
}
break;
case '/logout':
response = handleLogoutCommand();
auth.logout();
game.resetGame();
break;
case '/end-session':
response = await handleEndSessionCommand(userContext, gameContext);
if (response.success) {
// reset session costs client-side
auth.resetSessionCosts && auth.resetSessionCosts();
// Persist conversation to Firebase if available
if (conversation && conversation.currentConversation && conversation.persistConversation && game.gameId) {
try {
const persisted = await conversation.persistConversation(game.gameId);
if (persisted) {
response.message += ' Conversation history saved.';
}
else {
response.message += ' (Warning: Failed to save conversation history)';
}
}
catch (error) {
response.message += ' (Warning: Failed to save conversation history)';
}
}
}
break;
case '/login':
setIsLoading(true);
setLoadingMessage('Attempting to login...');
response = await handleLoginCommand();
if (response.success && response.userData) {
auth.login(response.userData);
game.fetchGameState();
}
setIsLoading(false);
break;
case '/register':
response = { success: false, message: "To register, please restart the application without a saved session, or use CLI flags." };
break;
case '/history':
setShowHistory(true);
return;
case '/model':
if (userContext.hasAICapability) {
// Show model selector overlay and stop further processing
setShowModelSelector(true);
return;
}
else {
response = { success: false, message: 'AI features not available. Please /login and use your API key to enable.' };
}
break;
case '/usage':
response = await handleUsageCommand(userContext, gameContext);
if (response.success && response.showUsageDashboard && response.usageData) {
setShowCostDashboard(response.usageData);
return;
}
break;
case '/cost':
response = await handleCostCommand(userContext, gameContext);
if (response.success && response.showCostMonitor && response.costData) {
setShowCostMonitor(response.costData);
return;
}
break;
// Removed /pricing, /compare, /estimate commands - simplified cost monitoring
default:
response = { success: false, message: `Unknown command: ${cmd}` };
}
if (response) {
const displayItems = prepareHistoryItems(displayResponse(response));
setHistory((prev) => [...prev, ...displayItems]);
// Track conversation if available
if (conversation && conversation.addEntry && game.gameId) {
try {
// Add user command entry
await conversation.addEntry({
type: 'user_input',
content: command,
metadata: {
requestType: 'command',
gameId: game.gameId,
roomId: game.currentRoomName,
},
});
// Add system response entry
if (response.message) {
await conversation.addEntry({
type: 'agent_response',
content: response.message,
metadata: {
requestType: 'command',
gameId: game.gameId,
roomId: game.currentRoomName,
},
});
}
}
catch (error) {
console.error('Failed to track command conversation entry:', error);
}
}
}
}
else {
// Natural language
setIsLoading(true);
// setLoadingMessage(`🤔 Thinking with ${selectedModel.label}...`);
setLoadingMessage(THINKING_MESSAGE[Math.floor(Math.random() * THINKING_MESSAGE.length)]);
const userContext = {
userId: auth.userId,
sessionToken: auth.sessionToken,
userName: auth.userName,
cliApiKey: auth.apiKey,
hasAICapability: auth.apiKey ? true : false,
};
const response = await handleNaturalLanguageCommand(command, userContext, selectedModel);
if (response) {
const displayItems = prepareHistoryItems(displayResponse(response));
setHistory((prev) => [...prev, ...displayItems]);
// Track natural language conversation
if (conversation && conversation.addEntry && game.gameId) {
try {
// Add user input entry
await conversation.addEntry({
type: 'user_input',
content: command,
metadata: {
model: selectedModel.value,
requestType: 'natural_language',
gameId: game.gameId,
roomId: game.currentRoomName,
},
});
// Add AI response entry
if (response.message) {
const totalTokens = response.usage?.tokens ?? ((response.usage?.inputTokens || 0) + (response.usage?.outputTokens || 0));
await conversation.addEntry({
type: 'agent_response',
content: response.message,
tokenUsage: response.usage ? {
inputTokens: response.usage.inputTokens ?? 0,
outputTokens: response.usage.outputTokens ?? 0,
// reasoningTokens: response.usage.reasoningTokens,
totalTokens,
} : undefined,
metadata: {
model: response.usage?.model || selectedModel.value,
requestType: 'natural_language',
gameId: game.gameId,
roomId: game.currentRoomName,
cost: response.usage?.cost,
},
// reasoningProcess: response.reasoning?.steps?.length
// ? response.reasoning.steps.join('\n')
// : response.reasoning?.summary,
});
}
}
catch (error) {
console.error('Failed to track natural language conversation entry:', error);
}
}
}
setIsLoading(false);
}
};