From 304f59872d4b99748e3b76c5d0e3e5d82b5c7f13 Mon Sep 17 00:00:00 2001 From: komatsu Date: Thu, 2 Oct 2025 14:01:57 +0900 Subject: [PATCH] feat: script function --- hatchling/app.py | 7 +++- hatchling/config/languages/en.toml | 5 +++ hatchling/config/languages/fr.toml | 5 +++ hatchling/ui/base_commands.py | 22 +++++++++++- hatchling/ui/chat_command_handler.py | 6 ++-- hatchling/ui/cli_chat.py | 52 +++++++++++++++++++++++----- 6 files changed, 85 insertions(+), 12 deletions(-) diff --git a/hatchling/app.py b/hatchling/app.py index 7e2f193..0777ee8 100644 --- a/hatchling/app.py +++ b/hatchling/app.py @@ -1,5 +1,6 @@ import asyncio import sys +import argparse from hatchling.core.logging.logging_config import configure_logging from hatchling.core.logging.logging_manager import logging_manager from hatchling.config.settings_registry import SettingsRegistry @@ -25,6 +26,10 @@ async def main_async(): Raises: Exception: Any unhandled exceptions that occur during execution. """ + parser = argparse.ArgumentParser(description="Hatchling CLI Chat Interface") + parser.add_argument("-f", "--input-file", type=str, help="Path to a file containing commands to execute.") + args = parser.parse_args() + settings_registry = None try: @@ -35,7 +40,7 @@ async def main_async(): default_language_code=settings_registry.settings.ui.language_code) # Create and run CLI chat interface - cli_chat = CLIChat(settings_registry) + cli_chat = CLIChat(settings_registry, input_file_path=args.input_file) await cli_chat.initialize_and_run() diff --git a/hatchling/config/languages/en.toml b/hatchling/config/languages/en.toml index 6753945..3a6fc89 100644 --- a/hatchling/config/languages/en.toml +++ b/hatchling/config/languages/en.toml @@ -259,6 +259,11 @@ disable_tools_description = "Disable MCP tools" version_name = "version" version_description = "Display the current version of Hatchling" +# Load command +[commands.load] +description = "Execute commands from a file" +args_description = "Path to the file containing commands" + # Hatch commands [commands.hatch] diff --git a/hatchling/config/languages/fr.toml b/hatchling/config/languages/fr.toml index 9a108dc..672a2ab 100644 --- a/hatchling/config/languages/fr.toml +++ b/hatchling/config/languages/fr.toml @@ -258,6 +258,11 @@ disable_tools_description = "Désactiver les outils MCP" version_name = "version" version_description = "Afficher la version actuelle de Hatchling" +# Load command +[commands.load] +description = "Exécuter les commandes depuis un fichier" +args_description = "Chemin vers le fichier contenant les commandes" + # Hatch commands [commands.hatch] env_create_name = "env:créer" diff --git a/hatchling/ui/base_commands.py b/hatchling/ui/base_commands.py index 59b8063..5fb149a 100644 --- a/hatchling/ui/base_commands.py +++ b/hatchling/ui/base_commands.py @@ -18,6 +18,10 @@ class BaseChatCommands(AbstractCommands): """Handles processing of command inputs in the chat interface.""" + def __init__(self, chat_session, settings_registry, style, cli_chat): + super().__init__(chat_session, settings_registry, style) + self.cli_chat = cli_chat + def _register_commands(self) -> None: """Register all available chat commands with their handlers.""" # New standardized command registration format with i18n support @@ -79,6 +83,13 @@ def _register_commands(self) -> None: 'description': translate("commands.base.version_description"), 'is_async': False, 'args': {} + }, + ':load': { + 'handler': self._cmd_load, + 'description': translate("commands.load.description"), + 'is_async': True, + 'args_description': translate("commands.load.args_description"), + 'example': ":load my_script.txt" } } @@ -196,4 +207,13 @@ def _cmd_version(self, _: str) -> bool: bool: True to continue the chat session. """ self.logger.info(f"Hatchling version: {__version__}") - return True \ No newline at end of file + return True + + async def _cmd_load(self, filename: str) -> bool: + """Handles the :load command to execute commands from a file.""" + if not filename: + self.logger.error("Error: Please provide a filename for the :load command.") + return True # Continue interactive session + + await self.cli_chat._process_commands_from_file(filename) + return True # Continue interactive session \ No newline at end of file diff --git a/hatchling/ui/chat_command_handler.py b/hatchling/ui/chat_command_handler.py index c924b54..2da5aa7 100644 --- a/hatchling/ui/chat_command_handler.py +++ b/hatchling/ui/chat_command_handler.py @@ -24,18 +24,20 @@ class ChatCommandHandler: """Handles processing of command inputs in the chat interface.""" - def __init__(self, chat_session, settings_registry: SettingsRegistry, style: Optional[Style] = None): + def __init__(self, chat_session, settings_registry: SettingsRegistry, style: Optional[Style] = None, cli_chat=None): """Initialize the command handler. Args: chat_session: The chat session this handler is associated with. settings_registry (SettingsRegistry): The settings registry containing configuration. style (Optional[Style]): Style for formatting command output. + cli_chat: The CLIChat instance for accessing its methods. """ self.settings_registry = settings_registry - self.base_commands = BaseChatCommands(chat_session, settings_registry, style) + self.cli_chat = cli_chat + self.base_commands = BaseChatCommands(chat_session, settings_registry, style, cli_chat) self.hatch_commands = HatchCommands(chat_session, settings_registry, style) self.settings_commands = SettingsCommands(chat_session, settings_registry, style) self.mcp_commands = MCPCommands(chat_session, settings_registry, style) diff --git a/hatchling/ui/cli_chat.py b/hatchling/ui/cli_chat.py index a4591a0..563b552 100644 --- a/hatchling/ui/cli_chat.py +++ b/hatchling/ui/cli_chat.py @@ -18,14 +18,16 @@ class CLIChat: """Command-line interface for chat functionality.""" - def __init__(self, settings_registry: SettingsRegistry): + def __init__(self, settings_registry: SettingsRegistry, input_file_path: str = None): """Initialize the CLI chat interface. Args: settings (SettingsRegistry): The settings management instance containing configuration. + input_file_path (str, optional): Path to a file containing commands to execute. Defaults to None. """ # Store settings first self.settings_registry = settings_registry + self.input_file_path = input_file_path # Get a logger - styling is already configured at the application level self.logger = logging_manager.get_session("CLIChat") @@ -101,7 +103,7 @@ def __init__(self, settings_registry: SettingsRegistry): mcp_manager.publisher.subscribe(self.cli_event_subscriber) # Initialize command handler - self.cmd_handler = ChatCommandHandler(self.chat_session, self.settings_registry, self.command_style) + self.cmd_handler = ChatCommandHandler(self.chat_session, self.settings_registry, self.command_style, self) # Setup key bindings self.key_bindings = self._create_key_bindings() @@ -184,8 +186,12 @@ def _get_right_prompt(self) -> FormattedText: return FormattedText([('class:right-prompt', right_prompt_text)]) async def start_interactive_session(self) -> None: - """Run an interactive chat session with message history.""" - + """Run an interactive chat session with message history or process commands from a file.""" + + if self.input_file_path: + await self._process_commands_from_file(self.input_file_path) + # After processing the file, continue to interactive mode + #async with aiohttp.ClientSession() as session: while True: try: @@ -196,7 +202,7 @@ async def start_interactive_session(self) -> None: ] # Use patch_stdout to prevent output interference with patch_stdout(): - user_message = await self.prompt_session.prompt_async( + user_input = await self.prompt_session.prompt_async( FormattedText(prompt_message), completer=self.cmd_handler.command_completer, lexer=self.cmd_handler.command_lexer, @@ -207,14 +213,14 @@ async def start_interactive_session(self) -> None: ) # Process as command if applicable - is_command, should_continue = await self.cmd_handler.process_command(user_message) + is_command, should_continue = await self.cmd_handler.process_command(user_input) if is_command: if not should_continue: break continue # Handle normal message - if not user_message.strip(): + if not user_input.strip(): # Skip empty input continue @@ -226,7 +232,7 @@ async def start_interactive_session(self) -> None: try: # Send the message (this will trigger streaming events) - await self.chat_session.send_message(user_message) + await self.chat_session.send_message(user_input) # Wait for all processing to complete (tool chains, etc.) await self._monitor_right_to_prompt() @@ -244,6 +250,36 @@ async def start_interactive_session(self) -> None: except Exception as e: self.logger.error(f"Error: {e}") print_pt(FormattedText([('red', f'\nError: {e}')])) + + async def _process_commands_from_file(self, file_path: str) -> None: + """Processes commands from a given file path.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + for line in f: + user_input = line.strip() + if not user_input: + continue + + print_pt(FormattedText([('green', f'You: {user_input}')])) + + is_command, should_continue = await self.cmd_handler.process_command(user_input) + if is_command: + if not should_continue: + break + continue + + self.cli_event_subscriber.set_processing_user_message(True) + try: + await self.chat_session.send_message(user_input) + await self._monitor_right_to_prompt() + except Exception as send_error: + self.cli_event_subscriber.set_processing_user_message(False) + self.logger.error(f"Error processing message from file: {send_error}") + print_pt('') + except FileNotFoundError: + self.logger.error(f"Error: Input file not found at {file_path}") + except Exception as e: + self.logger.error(f"Error processing input file: {e}") async def initialize_and_run(self) -> None: """Initialize the environment and run the interactive chat session."""