● Terminal Client Improvement Plan - Part 4: Detailed Help System Overview Enhance the REPL help system with detailed documentation, examples, and better formatting. This makes the terminal client self-documenting and easier to use for new developers. Files to Create 1. Create backend/terminal_client/help_text.py """ Help text and documentation for terminal client commands. Provides detailed, formatted help text for all REPL commands with usage examples and option descriptions. Author: Claude Date: 2025-10-27 """ from typing import Dict from rich.console import Console from rich.table import Table from rich.panel import Panel from rich.markdown import Markdown from rich import box console = Console() class HelpFormatter: """Format and display help text for commands.""" @staticmethod def show_command_help(command_name: str, help_data: Dict) -> None: """ Display detailed help for a specific command. Args: command_name: Name of the command help_data: Dictionary with help information { 'summary': 'Brief description', 'usage': 'command [OPTIONS]', 'options': [ {'name': '--option', 'type': 'TYPE', 'desc': 'Description'} ], 'examples': ['example 1', 'example 2'] } """ # Build help text help_text = [] # Summary help_text.append(f"**{command_name}** - {help_data.get('summary', 'No description')}") help_text.append("") # Usage if 'usage' in help_data: help_text.append("**USAGE:**") help_text.append(f" {help_data['usage']}") help_text.append("") # Options if 'options' in help_data and help_data['options']: help_text.append("**OPTIONS:**") # Create options table table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2)) table.add_column("Option", style="cyan", no_wrap=True) table.add_column("Type", style="yellow") table.add_column("Description", style="white") for opt in help_data['options']: table.add_row( opt['name'], opt.get('type', ''), opt.get('desc', '') ) console.print(table) console.print() # Examples if 'examples' in help_data and help_data['examples']: help_text.append("**EXAMPLES:**") for example in help_data['examples']: help_text.append(f" {example}") help_text.append("") # Display in panel if help_text: md = Markdown("\n".join(help_text[:3])) # Just summary and usage panel = Panel( md, title=f"[bold cyan]Help: {command_name}[/bold cyan]", border_style="cyan", box=box.ROUNDED ) console.print(panel) # Print rest outside panel for better formatting if len(help_text) > 3: console.print() for line in help_text[3:]: if line.startswith('**'): console.print(line.replace('**', ''), style="bold cyan") else: console.print(line) @staticmethod def show_command_list() -> None: """Display list of all available commands.""" console.print("\n[bold cyan]Available Commands:[/bold cyan]\n") # Game Management console.print("[bold yellow]Game Management:[/bold yellow]") console.print(" new_game Create a new game with test lineups and start it") console.print(" list_games List all games in state manager") console.print(" use_game Switch to a different game") console.print(" status Display current game state") console.print(" box_score Display box score") console.print() # Gameplay console.print("[bold yellow]Gameplay:[/bold yellow]") console.print(" defensive Submit defensive decision") console.print(" offensive Submit offensive decision") console.print(" resolve Resolve the current play") console.print(" quick_play Auto-play multiple plays") console.print() # Utilities console.print("[bold yellow]Utilities:[/bold yellow]") console.print(" config Show configuration") console.print(" clear Clear the screen") console.print(" help Show help for commands") console.print(" quit/exit Exit the REPL") console.print() console.print("[dim]Type 'help ' for detailed information.[/dim]") console.print("[dim]Use TAB for auto-completion of commands and options.[/dim]\n") # Detailed help data for each command HELP_DATA = { 'new_game': { 'summary': 'Create a new game with test lineups and start it immediately', 'usage': 'new_game [--league LEAGUE] [--home-team ID] [--away-team ID]', 'options': [ { 'name': '--league', 'type': 'sba|pd', 'desc': 'League type (default: sba)' }, { 'name': '--home-team', 'type': 'INT', 'desc': 'Home team ID (default: 1)' }, { 'name': '--away-team', 'type': 'INT', 'desc': 'Away team ID (default: 2)' } ], 'examples': [ 'new_game', 'new_game --league pd', 'new_game --league sba --home-team 5 --away-team 3' ] }, 'defensive': { 'summary': 'Submit defensive decision for the current play', 'usage': 'defensive [--alignment TYPE] [--infield DEPTH] [--outfield DEPTH] [--hold BASES]', 'options': [ { 'name': '--alignment', 'type': 'STRING', 'desc': 'Defensive alignment: normal, shifted_left, shifted_right, extreme_shift (default: normal)' }, { 'name': '--infield', 'type': 'STRING', 'desc': 'Infield depth: in, normal, back, double_play (default: normal)' }, { 'name': '--outfield', 'type': 'STRING', 'desc': 'Outfield depth: in, normal, back (default: normal)' }, { 'name': '--hold', 'type': 'LIST', 'desc': 'Comma-separated bases to hold runners: 1,2,3 (default: none)' } ], 'examples': [ 'defensive', 'defensive --alignment shifted_left', 'defensive --infield double_play --hold 1,3', 'defensive --alignment extreme_shift --infield back --outfield back' ] }, 'offensive': { 'summary': 'Submit offensive decision for the current play', 'usage': 'offensive [--approach TYPE] [--steal BASES] [--hit-run] [--bunt]', 'options': [ { 'name': '--approach', 'type': 'STRING', 'desc': 'Batting approach: normal, contact, power, patient (default: normal)' }, { 'name': '--steal', 'type': 'LIST', 'desc': 'Comma-separated bases to steal: 2,3 (default: none)' }, { 'name': '--hit-run', 'type': 'FLAG', 'desc': 'Execute hit-and-run play (default: false)' }, { 'name': '--bunt', 'type': 'FLAG', 'desc': 'Attempt bunt (default: false)' } ], 'examples': [ 'offensive', 'offensive --approach power', 'offensive --steal 2', 'offensive --steal 2,3 --hit-run', 'offensive --approach contact --bunt' ] }, 'resolve': { 'summary': 'Resolve the current play using submitted decisions', 'usage': 'resolve', 'options': [], 'examples': [ 'resolve' ], 'notes': 'Both defensive and offensive decisions must be submitted before resolving.' }, 'quick_play': { 'summary': 'Auto-play multiple plays with default decisions', 'usage': 'quick_play [COUNT]', 'options': [ { 'name': 'COUNT', 'type': 'INT', 'desc': 'Number of plays to execute (default: 1). Positional argument.' } ], 'examples': [ 'quick_play', 'quick_play 10', 'quick_play 27 # Play roughly 3 innings', 'quick_play 100 # Play full game quickly' ] }, 'status': { 'summary': 'Display current game state', 'usage': 'status', 'options': [], 'examples': [ 'status' ] }, 'box_score': { 'summary': 'Display box score for the current game', 'usage': 'box_score', 'options': [], 'examples': [ 'box_score' ] }, 'list_games': { 'summary': 'List all games currently loaded in state manager', 'usage': 'list_games', 'options': [], 'examples': [ 'list_games' ] }, 'use_game': { 'summary': 'Switch to a different game', 'usage': 'use_game ', 'options': [ { 'name': 'GAME_ID', 'type': 'UUID', 'desc': 'UUID of the game to switch to. Positional argument.' } ], 'examples': [ 'use_game a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'use_game # Use tab completion to see available games' ] }, 'config': { 'summary': 'Show terminal client configuration', 'usage': 'config', 'options': [], 'examples': [ 'config' ] }, 'clear': { 'summary': 'Clear the screen', 'usage': 'clear', 'options': [], 'examples': [ 'clear' ] } } def get_help_text(command: str) -> Dict: """ Get help data for a command. Args: command: Command name Returns: Help data dictionary or empty dict if not found """ return HELP_DATA.get(command, {}) def show_help(command: str = None) -> None: """ Show help for a command or list all commands. Args: command: Command name or None for command list """ if command: help_data = get_help_text(command) if help_data: HelpFormatter.show_command_help(command, help_data) else: console.print(f"[yellow]No help available for '{command}'[/yellow]") console.print(f"[dim]Type 'help' to see all available commands.[/dim]") else: HelpFormatter.show_command_list() Files to Update 2. Update backend/terminal_client/repl.py # Add import at top from terminal_client.help_text import show_help, get_help_text, HelpFormatter # Update the intro to be more helpful class GameREPL(GameREPLCompletions, cmd.Cmd): """Interactive REPL for game engine testing.""" intro = """ ╔══════════════════════════════════════════════════════════════════════════════╗ ║ Paper Dynasty Game Engine - Terminal Client ║ ║ Interactive Mode ║ ╚══════════════════════════════════════════════════════════════════════════════╝ [cyan]Type 'help' to see all available commands.[/cyan] [cyan]Type 'help ' for detailed information about a specific command.[/cyan] [cyan]Use TAB for auto-completion of commands and options.[/cyan] [yellow]Quick start:[/yellow] new_game Create and start a new game status Show current game state defensive Submit defensive decision offensive Submit offensive decision resolve Resolve the play quick_play 10 Auto-play 10 plays Press Ctrl+D or type 'quit' to exit. """ prompt = '⚾ > ' # ... rest of __init__ stays the same ... # ==================== Enhanced Help System ==================== def do_help(self, arg): """ Show help for commands. Usage: help List all commands help Show detailed help for a command """ if arg: # Show detailed help for specific command show_help(arg) else: # Show command list HelpFormatter.show_command_list() def help_new_game(self): """Show detailed help for new_game command.""" show_help('new_game') def help_defensive(self): """Show detailed help for defensive command.""" show_help('defensive') def help_offensive(self): """Show detailed help for offensive command.""" show_help('offensive') def help_resolve(self): """Show detailed help for resolve command.""" show_help('resolve') def help_quick_play(self): """Show detailed help for quick_play command.""" show_help('quick_play') def help_status(self): """Show detailed help for status command.""" show_help('status') def help_box_score(self): """Show detailed help for box_score command.""" show_help('box_score') def help_list_games(self): """Show detailed help for list_games command.""" show_help('list_games') def help_use_game(self): """Show detailed help for use_game command.""" show_help('use_game') def help_config(self): """Show detailed help for config command.""" show_help('config') def help_clear(self): """Show detailed help for clear command.""" show_help('clear') # ==================== Keep existing command methods ==================== # All do_* methods remain unchanged 3. Create backend/terminal_client/__main__.py Update # Add help command for standalone mode # Add this at the bottom of main.py @cli.command('help') @click.argument('command', required=False) def show_cli_help(command): """ Show help for terminal client commands. Usage: python -m terminal_client help # Show all commands python -m terminal_client help new-game # Show help for specific command """ from terminal_client.help_text import show_help # Convert hyphenated command to underscore for lookup if command: command = command.replace('-', '_') show_help(command) Testing Plan 4. Create backend/tests/unit/terminal_client/test_help_text.py """ Unit tests for help text system. """ import pytest from io import StringIO from unittest.mock import patch from terminal_client.help_text import ( HelpFormatter, get_help_text, show_help, HELP_DATA ) class TestHelpData: """Tests for help data structure.""" def test_all_commands_have_help(self): """Test that all major commands have help data.""" required_commands = [ 'new_game', 'defensive', 'offensive', 'resolve', 'quick_play', 'status', 'use_game', 'list_games' ] for cmd in required_commands: assert cmd in HELP_DATA, f"Missing help data for {cmd}" def test_help_data_structure(self): """Test that help data has required fields.""" for cmd, data in HELP_DATA.items(): assert 'summary' in data, f"{cmd} missing summary" assert 'usage' in data, f"{cmd} missing usage" assert 'options' in data, f"{cmd} missing options" assert 'examples' in data, f"{cmd} missing examples" # Validate options structure for opt in data['options']: assert 'name' in opt, f"{cmd} option missing name" assert 'desc' in opt, f"{cmd} option missing desc" def test_get_help_text_valid(self): """Test getting help text for valid command.""" result = get_help_text('new_game') assert result is not None assert 'summary' in result def test_get_help_text_invalid(self): """Test getting help text for invalid command.""" result = get_help_text('nonexistent_command') assert result == {} class TestHelpFormatter: """Tests for HelpFormatter.""" @patch('terminal_client.help_text.console') def test_show_command_help(self, mock_console): """Test showing help for a command.""" help_data = { 'summary': 'Test command', 'usage': 'test [OPTIONS]', 'options': [ {'name': '--option', 'type': 'STRING', 'desc': 'Test option'} ], 'examples': ['test --option value'] } HelpFormatter.show_command_help('test', help_data) # Verify console.print was called assert mock_console.print.called @patch('terminal_client.help_text.console') def test_show_command_list(self, mock_console): """Test showing command list.""" HelpFormatter.show_command_list() # Verify console.print was called multiple times assert mock_console.print.call_count > 5 class TestShowHelp: """Tests for show_help function.""" @patch('terminal_client.help_text.HelpFormatter.show_command_help') def test_show_help_specific_command(self, mock_show): """Test showing help for specific command.""" show_help('new_game') mock_show.assert_called_once() args = mock_show.call_args[0] assert args[0] == 'new_game' assert 'summary' in args[1] @patch('terminal_client.help_text.HelpFormatter.show_command_list') def test_show_help_no_command(self, mock_list): """Test showing command list when no command specified.""" show_help() mock_list.assert_called_once() @patch('terminal_client.help_text.console') def test_show_help_invalid_command(self, mock_console): """Test showing help for invalid command.""" show_help('invalid_command') # Should print warning message assert mock_console.print.called call_args = str(mock_console.print.call_args) assert 'No help available' in call_args Example Output Command List (help): ⚾ > help Available Commands: Game Management: new_game Create a new game with test lineups and start it list_games List all games in state manager use_game Switch to a different game status Display current game state box_score Display box score Gameplay: defensive Submit defensive decision offensive Submit offensive decision resolve Resolve the current play quick_play Auto-play multiple plays Utilities: config Show configuration clear Clear the screen help Show help for commands quit/exit Exit the REPL Type 'help ' for detailed information. Use TAB for auto-completion of commands and options. Detailed Help (help defensive): ⚾ > help defensive ╭─────────────── Help: defensive ───────────────╮ │ defensive - Submit defensive decision for │ │ the current play │ │ │ │ USAGE: │ │ defensive [--alignment TYPE] [--infield │ │ DEPTH] [--outfield DEPTH] [--hold BASES] │ ╰───────────────────────────────────────────────╯ OPTIONS: --alignment STRING Defensive alignment: normal, shifted_left, shifted_right, extreme_shift (default: normal) --infield STRING Infield depth: in, normal, back, double_play (default: normal) --outfield STRING Outfield depth: in, normal, back (default: normal) --hold LIST Comma-separated bases to hold runners: 1,2,3 (default: none) EXAMPLES: defensive defensive --alignment shifted_left defensive --infield double_play --hold 1,3 defensive --alignment extreme_shift --infield back --outfield back Benefits 1. Self-documenting: No need to check external docs for basic usage 2. Rich formatting: Beautiful output with colors, tables, and panels 3. Comprehensive: Every option explained with examples 4. Discoverable: Easy to explore what's available 5. Consistent: Same help format across all commands 6. Learning tool: Examples teach proper usage patterns