From a30627d23bdb77a76caea15c0f4ec44e14525488 Mon Sep 17 00:00:00 2001 From: vasco Date: Sat, 7 Mar 2026 10:01:49 +0100 Subject: [PATCH 1/4] refactoring --- .../tui/widgets/richlog_visualizer.py | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/openhands_cli/tui/widgets/richlog_visualizer.py b/openhands_cli/tui/widgets/richlog_visualizer.py index 817e82e2..61b37560 100644 --- a/openhands_cli/tui/widgets/richlog_visualizer.py +++ b/openhands_cli/tui/widgets/richlog_visualizer.py @@ -538,14 +538,18 @@ def _truncate_for_display( return ELLIPSIS + text[-(max_length - len(ELLIPSIS)) :] return text + def _clean_and_truncate(self, text: str, *, from_start: bool = True) -> str: + """Strip, collapse newlines, truncate, and escape Rich markup for display.""" + text = str(text).strip().replace("\n", " ") + text = self._truncate_for_display(text, from_start=from_start) + return self._escape_rich_markup(text) + def _extract_meaningful_title(self, event, fallback_title: str) -> str: """Extract a meaningful title from an event, with fallback to truncated content.""" # For ActionEvents, prefer the LLM-generated summary if available if hasattr(event, "summary") and event.summary: - summary = str(event.summary).strip().replace("\n", " ") - summary = self._truncate_for_display(summary) - return self._escape_rich_markup(summary) + return self._clean_and_truncate(event.summary) # Try to extract meaningful information from the event if hasattr(event, "action") and event.action is not None: @@ -555,42 +559,27 @@ def _extract_meaningful_title(self, event, fallback_title: str) -> str: # Try to get specific details based on action type if hasattr(action, "command") and action.command: - # For command actions, show the command - cmd = str(action.command).strip() - cmd = self._truncate_for_display(cmd) - return f"{action_type}: {self._escape_rich_markup(cmd)}" + return f"{action_type}: {self._clean_and_truncate(action.command)}" elif hasattr(action, "path") and action.path: - # For file actions, show the path (truncate from start to show filename) - path = str(action.path) - path = self._truncate_for_display(path, from_start=False) - return f"{action_type}: {self._escape_rich_markup(path)}" + # For file actions, truncate from start to show filename + return f"{action_type}: {self._clean_and_truncate(action.path, from_start=False)}" elif hasattr(action, "content") and action.content: - # For content-based actions, show truncated content - content = str(action.content).strip().replace("\n", " ") - content = self._truncate_for_display(content) - return f"{action_type}: {self._escape_rich_markup(content)}" + return f"{action_type}: {self._clean_and_truncate(action.content)}" elif hasattr(action, "message") and action.message: - # For message actions, show truncated message - msg = str(action.message).strip().replace("\n", " ") - msg = self._truncate_for_display(msg) - return f"{action_type}: {self._escape_rich_markup(msg)}" + return f"{action_type}: {self._clean_and_truncate(action.message)}" else: return f"{action_type} Action" elif hasattr(event, "observation") and event.observation is not None: - # For ObservationEvents, try to get observation details obs = event.observation obs_type = obs.__class__.__name__.replace("Observation", "") if hasattr(obs, "content") and obs.content: - content = str(obs.content).strip().replace("\n", " ") - content = self._truncate_for_display(content) - return f"{obs_type}: {self._escape_rich_markup(content)}" + return f"{obs_type}: {self._clean_and_truncate(obs.content)}" else: return f"{obs_type} Observation" elif hasattr(event, "llm_message") and event.llm_message is not None: - # For MessageEvents, show truncated message content msg = event.llm_message if hasattr(msg, "content") and msg.content: # Extract text from content list (content is a list of TextContent @@ -605,16 +594,11 @@ def _extract_meaningful_title(self, event, fallback_title: str) -> str: else: content_text = str(msg.content) - content_text = content_text.strip().replace("\n", " ") - content_text = self._truncate_for_display(content_text) role = "User" if msg.role == "user" else "Agent" - return f"{role}: {self._escape_rich_markup(content_text)}" + return f"{role}: {self._clean_and_truncate(content_text)}" elif hasattr(event, "message") and event.message: - # For events with direct message attribute - content = str(event.message).strip().replace("\n", " ") - content = self._truncate_for_display(content) - return f"{fallback_title}: {self._escape_rich_markup(content)}" + return f"{fallback_title}: {self._clean_and_truncate(event.message)}" # If we can't extract meaningful info, try to truncate the visualized content if hasattr(event, "visualize"): From b5598c897b7e550656e3dc0a3b1744b22506eec6 Mon Sep 17 00:00:00 2001 From: vasco Date: Sat, 7 Mar 2026 10:09:43 +0100 Subject: [PATCH 2/4] fix pre-commit --- openhands_cli/tui/widgets/richlog_visualizer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openhands_cli/tui/widgets/richlog_visualizer.py b/openhands_cli/tui/widgets/richlog_visualizer.py index 61b37560..5101de7d 100644 --- a/openhands_cli/tui/widgets/richlog_visualizer.py +++ b/openhands_cli/tui/widgets/richlog_visualizer.py @@ -562,7 +562,13 @@ def _extract_meaningful_title(self, event, fallback_title: str) -> str: return f"{action_type}: {self._clean_and_truncate(action.command)}" elif hasattr(action, "path") and action.path: # For file actions, truncate from start to show filename - return f"{action_type}: {self._clean_and_truncate(action.path, from_start=False)}" + + return f"{action_type}: { + self._clean_and_truncate( + action.path, + from_start=False, + ) + }" elif hasattr(action, "content") and action.content: return f"{action_type}: {self._clean_and_truncate(action.content)}" elif hasattr(action, "message") and action.message: From 0c76fe011c2e52a9d494d7ba2269ee7835e2ae84 Mon Sep 17 00:00:00 2001 From: vasco Date: Sat, 7 Mar 2026 10:26:05 +0100 Subject: [PATCH 3/4] refactoring --- openhands_cli/auth/api_client.py | 135 ++++++++------------------- openhands_cli/auth/device_flow.py | 43 ++++----- openhands_cli/auth/login_command.py | 50 ++++------ openhands_cli/auth/logout_command.py | 25 ++--- openhands_cli/auth/utils.py | 32 ++++--- openhands_cli/cloud/command.py | 35 +++---- openhands_cli/cloud/conversation.py | 45 ++++----- 7 files changed, 138 insertions(+), 227 deletions(-) diff --git a/openhands_cli/auth/api_client.py b/openhands_cli/auth/api_client.py index 5044ee73..91143906 100644 --- a/openhands_cli/auth/api_client.py +++ b/openhands_cli/auth/api_client.py @@ -111,38 +111,26 @@ async def get_conversation_info( def _print_settings_summary(settings: dict[str, Any]) -> None: - console_print( - f"[{OPENHANDS_THEME.success}] ✓ User " - f"settings retrieved[/{OPENHANDS_THEME.success}]" - ) + console_print(" ✓ User settings retrieved", style=OPENHANDS_THEME.success) llm_model = settings.get("llm_model", "Not set") agent_name = settings.get("agent", "Not set") language = settings.get("language", "Not set") llm_api_key_set = settings.get("llm_api_key_set", False) - console_print( - f" [{OPENHANDS_THEME.secondary}]LLM Model: " - f"{llm_model}[/{OPENHANDS_THEME.secondary}]" - ) - console_print( - f" [{OPENHANDS_THEME.secondary}]Agent: " - f"{agent_name}[/{OPENHANDS_THEME.secondary}]" - ) - console_print( - f" [{OPENHANDS_THEME.secondary}]Language: " - f"{language}[/{OPENHANDS_THEME.secondary}]" - ) + console_print(f" LLM Model: {llm_model}", style=OPENHANDS_THEME.secondary) + console_print(f" Agent: {agent_name}", style=OPENHANDS_THEME.secondary) + console_print(f" Language: {language}", style=OPENHANDS_THEME.secondary) if llm_api_key_set: console_print( - f" [{OPENHANDS_THEME.success}]✓ LLM API key is configured in " - f"settings[/{OPENHANDS_THEME.success}]" + " ✓ LLM API key is configured in settings", + style=OPENHANDS_THEME.success, ) else: console_print( - f" [{OPENHANDS_THEME.warning}]! No LLM API key configured in " - f"settings[/{OPENHANDS_THEME.warning}]" + " ! No LLM API key configured in settings", + style=OPENHANDS_THEME.warning, ) @@ -163,12 +151,12 @@ def _ask_user_consent_for_overwrite( True if user consents to overwrite, False otherwise """ console_print( - f"\n[{OPENHANDS_THEME.warning}]⚠️ Existing agent configuration found!" - f"[/{OPENHANDS_THEME.warning}]" + "\n⚠️ Existing agent configuration found!", style=OPENHANDS_THEME.warning ) console_print( - f"[{OPENHANDS_THEME.secondary}]This will overwrite your current settings with " - f"the ones from OpenHands Cloud.[/{OPENHANDS_THEME.secondary}]\n" + "This will overwrite your current settings with " + "the ones from OpenHands Cloud.\n", + style=OPENHANDS_THEME.secondary, ) # Show current vs new settings comparison @@ -176,10 +164,7 @@ def _ask_user_consent_for_overwrite( new_model = new_settings.get("llm_model", default_model) base_url = new_settings.get("llm_base_url", None) - console_print( - f"[{OPENHANDS_THEME.secondary}]Current " - f"configuration:[/{OPENHANDS_THEME.secondary}]" - ) + console_print("Current configuration:", style=OPENHANDS_THEME.secondary) console_print( f" • Model: [{OPENHANDS_THEME.accent}]{html.escape(current_model)}" f"[/{OPENHANDS_THEME.accent}]" @@ -191,10 +176,7 @@ def _ask_user_consent_for_overwrite( f"{html.escape(existing_agent.llm.base_url)}[/{OPENHANDS_THEME.accent}]" ) - console_print( - f"\n[{OPENHANDS_THEME.secondary}]New configuration from " - f"cloud:[/{OPENHANDS_THEME.secondary}]" - ) + console_print("\nNew configuration from cloud:", style=OPENHANDS_THEME.secondary) console_print( f" • Model: [{OPENHANDS_THEME.accent}]{html.escape(new_model)}" f"[/{OPENHANDS_THEME.accent}]" @@ -250,49 +232,32 @@ def create_and_save_agent_configuration( ) console_print( - f"[{OPENHANDS_THEME.success}]✓ Agent configuration created and " - f"saved![/{OPENHANDS_THEME.success}]" - ) - console_print( - f"[{OPENHANDS_THEME.secondary}]Configuration " - f"details:[/{OPENHANDS_THEME.secondary}]" + "✓ Agent configuration created and saved!", style=OPENHANDS_THEME.success ) + console_print("Configuration details:", style=OPENHANDS_THEME.secondary) llm = agent.llm - console_print( - f" • Model: [{OPENHANDS_THEME.accent}]{llm.model}[/{OPENHANDS_THEME.accent}]" - ) - console_print( - f" • Base URL: [{OPENHANDS_THEME.accent}]{llm.base_url}" - f"[/{OPENHANDS_THEME.accent}]" - ) - console_print( - f" • Usage ID: [{OPENHANDS_THEME.accent}]{llm.usage_id}" - f"[/{OPENHANDS_THEME.accent}]" - ) - console_print( - f" • API Key: [{OPENHANDS_THEME.accent}]✓ Set[/{OPENHANDS_THEME.accent}]" - ) + console_print(f" • Model: {llm.model}", style=OPENHANDS_THEME.accent) + console_print(f" • Base URL: {llm.base_url}", style=OPENHANDS_THEME.accent) + console_print(f" • Usage ID: {llm.usage_id}", style=OPENHANDS_THEME.accent) + console_print(" • API Key: ✓ Set", style=OPENHANDS_THEME.accent) tools_count = len(agent.tools) console_print( - f" • Tools: [{OPENHANDS_THEME.accent}]{tools_count} default tools loaded" - f"[/{OPENHANDS_THEME.accent}]" + f" • Tools: {tools_count} default tools loaded", style=OPENHANDS_THEME.accent ) condenser = agent.condenser if isinstance(condenser, LLMSummarizingCondenser): console_print( - f" • Condenser: [{OPENHANDS_THEME.accent}]LLM Summarizing " + f" • Condenser: LLM Summarizing " f"(max_size: {condenser.max_size}, " - f"keep_first: {condenser.keep_first})[/{OPENHANDS_THEME.accent}]" + f"keep_first: {condenser.keep_first})", + style=OPENHANDS_THEME.accent, ) - console_print( - f" • Saved to: [{OPENHANDS_THEME.accent}]{get_settings_path()}" - f"[/{OPENHANDS_THEME.accent}]" - ) + console_print(f" • Saved to: {get_settings_path()}", style=OPENHANDS_THEME.accent) async def fetch_user_data_after_oauth( @@ -302,41 +267,29 @@ async def fetch_user_data_after_oauth( """Fetch user data after OAuth and optionally create & save an Agent.""" client = OpenHandsApiClient(server_url, api_key) - console_print( - f"[{OPENHANDS_THEME.accent}]Fetching user data...[/{OPENHANDS_THEME.accent}]" - ) + console_print("Fetching user data...", style=OPENHANDS_THEME.accent) try: # Fetch LLM API key - console_print( - f"[{OPENHANDS_THEME.secondary}]• Getting LLM API key..." - f"[/{OPENHANDS_THEME.secondary}]" - ) + console_print("• Getting LLM API key...", style=OPENHANDS_THEME.secondary) llm_api_key = await client.get_llm_api_key() if llm_api_key: console_print( - f"[{OPENHANDS_THEME.success}] ✓ LLM API key retrieved: " - f"{llm_api_key[:3]}...[/{OPENHANDS_THEME.success}]" + f" ✓ LLM API key retrieved: {llm_api_key[:3]}...", + style=OPENHANDS_THEME.success, ) else: - console_print( - f"[{OPENHANDS_THEME.warning}] ! No " - f"LLM API key available[/{OPENHANDS_THEME.warning}]" - ) + console_print(" ! No LLM API key available", style=OPENHANDS_THEME.warning) # Fetch user settings - console_print( - f"[{OPENHANDS_THEME.secondary}]• Getting user settings..." - f"[/{OPENHANDS_THEME.secondary}]" - ) + console_print("• Getting user settings...", style=OPENHANDS_THEME.secondary) settings = await client.get_user_settings() if settings: _print_settings_summary(settings) else: console_print( - f"[{OPENHANDS_THEME.warning}] ! No " - f"user settings available[/{OPENHANDS_THEME.warning}]" + " ! No user settings available", style=OPENHANDS_THEME.warning ) user_data = { @@ -351,33 +304,27 @@ async def fetch_user_data_after_oauth( except ValueError as e: # User declined to overwrite existing configuration console_print("\n") + console_print(str(e), style=OPENHANDS_THEME.warning) console_print( - f"[{OPENHANDS_THEME.warning}]{e}[/{OPENHANDS_THEME.warning}]" - ) - console_print( - f"[{OPENHANDS_THEME.secondary}]Keeping existing " - f"agent configuration.[/{OPENHANDS_THEME.secondary}]" + "Keeping existing agent configuration.", + style=OPENHANDS_THEME.secondary, ) except Exception as e: console_print( - f"[{OPENHANDS_THEME.warning}]Warning: Could not create " - f"agent configuration: {e}[/{OPENHANDS_THEME.warning}]" + f"Warning: Could not create agent configuration: {e}", + style=OPENHANDS_THEME.warning, ) else: console_print( - f"[{OPENHANDS_THEME.warning}]Skipping agent configuration; " - f"missing key or settings.[/{OPENHANDS_THEME.warning}]" + "Skipping agent configuration; missing key or settings.", + style=OPENHANDS_THEME.warning, ) console_print( - f"[{OPENHANDS_THEME.success}]✓ User data " - f"fetched successfully![/{OPENHANDS_THEME.success}]" + "✓ User data fetched successfully!", style=OPENHANDS_THEME.success ) return user_data except ApiClientError as e: - console_print( - f"[{OPENHANDS_THEME.error}]Error fetching user data: " - f"{e}[/{OPENHANDS_THEME.error}]" - ) + console_print(f"Error fetching user data: {e}", style=OPENHANDS_THEME.error) raise diff --git a/openhands_cli/auth/device_flow.py b/openhands_cli/auth/device_flow.py index f2145f29..59cf6245 100644 --- a/openhands_cli/auth/device_flow.py +++ b/openhands_cli/auth/device_flow.py @@ -161,55 +161,51 @@ async def authenticate(self) -> DeviceTokenResponse: DeviceFlowError: If authentication fails """ console_print( - f"[{OPENHANDS_THEME.accent}]Starting OpenHands authentication..." - f"[/{OPENHANDS_THEME.accent}]" + "Starting OpenHands authentication...", style=OPENHANDS_THEME.accent ) # Step 1: Start device flow try: auth_response = await self.start_device_flow() except DeviceFlowError as e: - console_print( - f"[{OPENHANDS_THEME.error}]Error: {e}[/{OPENHANDS_THEME.error}]" - ) + console_print(f"Error: {e}", style=OPENHANDS_THEME.error) raise # Step 2: Use verification_uri_complete if available, otherwise construct URL verification_url = auth_response.verification_uri_complete console_print( - f"\n[{OPENHANDS_THEME.warning}]Opening your web browser for " - f"authentication...[/{OPENHANDS_THEME.warning}]" + "\nOpening your web browser for authentication...", + style=OPENHANDS_THEME.warning, ) console_print( - f"[{OPENHANDS_THEME.secondary}]URL: [bold]{verification_url}[/bold]" - f"[/{OPENHANDS_THEME.secondary}]" + f"URL: [bold]{verification_url}[/bold]", + style=OPENHANDS_THEME.secondary, ) # Automatically open the browser try: webbrowser.open(verification_url) console_print( - f"[{OPENHANDS_THEME.success}]✓ Browser " - f"opened successfully[/{OPENHANDS_THEME.success}]" + "✓ Browser opened successfully", style=OPENHANDS_THEME.success ) except Exception as e: console_print( - f"[{OPENHANDS_THEME.warning}]Could not open browser automatically: " - f"{e}[/{OPENHANDS_THEME.warning}]" + f"Could not open browser automatically: {e}", + style=OPENHANDS_THEME.warning, ) console_print( - f"[{OPENHANDS_THEME.secondary}]Please manually open: " - f"[bold]{verification_url}[/bold][/{OPENHANDS_THEME.secondary}]" + f"Please manually open: [bold]{verification_url}[/bold]", + style=OPENHANDS_THEME.secondary, ) console_print( - f"[{OPENHANDS_THEME.secondary}]Follow the instructions in your browser " - f"to complete authentication[/{OPENHANDS_THEME.secondary}]" + "Follow the instructions in your browser to complete authentication", + style=OPENHANDS_THEME.secondary, ) console_print( - f"\n[{OPENHANDS_THEME.accent}]Waiting for authentication to complete..." - f"[/{OPENHANDS_THEME.accent}]" + "\nWaiting for authentication to complete...", + style=OPENHANDS_THEME.accent, ) # Step 3: Poll for token using device_code and interval from auth_response @@ -217,15 +213,10 @@ async def authenticate(self) -> DeviceTokenResponse: token_response = await self.poll_for_token( auth_response.device_code, auth_response.interval ) - console_print( - f"[{OPENHANDS_THEME.success}]✓ Authentication " - f"successful![/{OPENHANDS_THEME.success}]" - ) + console_print("✓ Authentication successful!", style=OPENHANDS_THEME.success) return token_response except DeviceFlowError as e: - console_print( - f"[{OPENHANDS_THEME.error}]Error: {e}[/{OPENHANDS_THEME.error}]" - ) + console_print(f"Error: {e}", style=OPENHANDS_THEME.error) raise diff --git a/openhands_cli/auth/login_command.py b/openhands_cli/auth/login_command.py index 83497efa..1c120bc4 100644 --- a/openhands_cli/auth/login_command.py +++ b/openhands_cli/auth/login_command.py @@ -24,12 +24,12 @@ async def _fetch_user_data_with_context( # Initial context output if already_logged_in: console_print( - f"[{OPENHANDS_THEME.warning}]You are already logged in to " - f"OpenHands Cloud.[/{OPENHANDS_THEME.warning}]" + "You are already logged in to OpenHands Cloud.", + style=OPENHANDS_THEME.warning, ) console_print( - f"[{OPENHANDS_THEME.secondary}]Pulling latest settings from remote..." - f"[/{OPENHANDS_THEME.secondary}]" + "Pulling latest settings from remote...", + style=OPENHANDS_THEME.secondary, ) # If already logged, skip re-fetching settings @@ -41,8 +41,8 @@ async def _fetch_user_data_with_context( # --- SUCCESS MESSAGES --- console_print( - f"\n[{OPENHANDS_THEME.success}]✓ Settings synchronized " - f"successfully![/{OPENHANDS_THEME.success}]" + "\n✓ Settings synchronized successfully!", + style=OPENHANDS_THEME.success, ) except ApiClientError as e: @@ -50,13 +50,13 @@ async def _fetch_user_data_with_context( safe_error = html.escape(str(e)) console_print( - f"\n[{OPENHANDS_THEME.warning}]Warning: " - f"Could not fetch user data: {safe_error}[/{OPENHANDS_THEME.warning}]" + f"\nWarning: Could not fetch user data: {safe_error}", + style=OPENHANDS_THEME.warning, ) + escaped_cmd = html.escape("openhands logout && openhands login") console_print( - f"[{OPENHANDS_THEME.secondary}]Please try: [bold]" - f"{html.escape('openhands logout && openhands login')}" - f"[/bold][/{OPENHANDS_THEME.secondary}]" + f"Please try: [bold]{escaped_cmd}[/bold]", + style=OPENHANDS_THEME.secondary, ) @@ -77,16 +77,13 @@ async def login_command(server_url: str, skip_settings_sync: bool = False) -> bo if existing_api_key and not await is_token_valid(server_url, existing_api_key): console_print( - f"[{OPENHANDS_THEME.warning}]Token is invalid or expired. " - f"Logging out...[/{OPENHANDS_THEME.warning}]" + "Token is invalid or expired. Logging out...", + style=OPENHANDS_THEME.warning, ) logout_command(server_url) # Proceed with normal login flow - console_print( - f"[{OPENHANDS_THEME.accent}]Logging in to OpenHands Cloud..." - f"[/{OPENHANDS_THEME.accent}]" - ) + console_print("Logging in to OpenHands Cloud...", style=OPENHANDS_THEME.accent) # Re-read token (may have been cleared by logout above) existing_api_key = token_storage.get_api_key() @@ -105,10 +102,7 @@ async def login_command(server_url: str, skip_settings_sync: bool = False) -> bo try: token_response = await authenticate_with_device_flow(server_url) except DeviceFlowError as e: - console_print( - f"[{OPENHANDS_THEME.error}]Authentication failed: " - f"{e}[/{OPENHANDS_THEME.error}]" - ) + console_print(f"Authentication failed: {e}", style=OPENHANDS_THEME.error) return False api_key = token_response.access_token @@ -116,13 +110,10 @@ async def login_command(server_url: str, skip_settings_sync: bool = False) -> bo # Store the API key securely token_storage.store_api_key(api_key) + console_print("✓ Logged into OpenHands Cloud", style=OPENHANDS_THEME.success) console_print( - f"[{OPENHANDS_THEME.success}]✓ Logged " - f"into OpenHands Cloud[/{OPENHANDS_THEME.success}]" - ) - console_print( - f"[{OPENHANDS_THEME.secondary}]Your authentication " - f"tokens have been stored securely.[/{OPENHANDS_THEME.secondary}]" + "Your authentication tokens have been stored securely.", + style=OPENHANDS_THEME.secondary, ) # Fetch user data and configure local agent @@ -147,8 +138,5 @@ def run_login_command(server_url: str) -> bool: try: return asyncio.run(login_command(server_url)) except KeyboardInterrupt: - console_print( - f"\n[{OPENHANDS_THEME.warning}]Login cancelled by " - f"user.[/{OPENHANDS_THEME.warning}]" - ) + console_print("\nLogin cancelled by user.", style=OPENHANDS_THEME.warning) return False diff --git a/openhands_cli/auth/logout_command.py b/openhands_cli/auth/logout_command.py index 0959a0e8..a6bff97f 100644 --- a/openhands_cli/auth/logout_command.py +++ b/openhands_cli/auth/logout_command.py @@ -20,20 +20,18 @@ def logout_command(server_url: str | None = None) -> bool: # Logging out from a specific server (conceptually; we only store one key) if server_url: console_print( - f"[{OPENHANDS_THEME.accent}]Logging out from OpenHands Cloud..." - f"[/{OPENHANDS_THEME.accent}]" + "Logging out from OpenHands Cloud...", style=OPENHANDS_THEME.accent ) was_logged_in = token_storage.remove_api_key() if was_logged_in: console_print( - f"[{OPENHANDS_THEME.success}]✓ Logged " - f"out of OpenHands Cloud[/{OPENHANDS_THEME.success}]" + "✓ Logged out of OpenHands Cloud", style=OPENHANDS_THEME.success ) else: console_print( - f"[{OPENHANDS_THEME.warning}]You were not logged in to " - f"OpenHands Cloud[/{OPENHANDS_THEME.warning}]" + "You were not logged in to OpenHands Cloud", + style=OPENHANDS_THEME.warning, ) return True @@ -41,26 +39,21 @@ def logout_command(server_url: str | None = None) -> bool: # Logging out globally (no server specified) if not token_storage.has_api_key(): console_print( - f"[{OPENHANDS_THEME.warning}]You are not logged in to " - f"OpenHands Cloud.[/{OPENHANDS_THEME.warning}]" + "You are not logged in to OpenHands Cloud.", + style=OPENHANDS_THEME.warning, ) return True console_print( - f"[{OPENHANDS_THEME.accent}]Logging out from OpenHands Cloud..." - f"[/{OPENHANDS_THEME.accent}]" + "Logging out from OpenHands Cloud...", style=OPENHANDS_THEME.accent ) token_storage.remove_api_key() - console_print( - f"[{OPENHANDS_THEME.success}]✓ Logged " - f"out of OpenHands Cloud[/{OPENHANDS_THEME.success}]" - ) + console_print("✓ Logged out of OpenHands Cloud", style=OPENHANDS_THEME.success) return True except Exception as e: console_print( - f"[{OPENHANDS_THEME.error}]Unexpected error during logout: " - f"{e}[/{OPENHANDS_THEME.error}]" + f"Unexpected error during logout: {e}", style=OPENHANDS_THEME.error ) return False diff --git a/openhands_cli/auth/utils.py b/openhands_cli/auth/utils.py index 2f306595..27fb24b7 100644 --- a/openhands_cli/auth/utils.py +++ b/openhands_cli/auth/utils.py @@ -16,9 +16,19 @@ _console = Console() -def console_print(message: str) -> None: - """Unified formatted print helper using rich console.""" - _console.print(message) +def console_print(message: str, *, style: str | None = None) -> None: + """Unified formatted print helper using rich console. + + Args: + message: Text to print (may contain Rich markup). + style: Optional OPENHANDS_THEME style name. When given, the message + is automatically wrapped in ``[{style}]…[/{style}]`` tags so + callers don't have to repeat the verbose markup pattern. + """ + if style: + _console.print(f"[{style}]{message}[/{style}]") + else: + _console.print(message) async def is_token_valid(server_url: str, api_key: str) -> bool: @@ -59,19 +69,17 @@ async def ensure_valid_auth(server_url: str) -> str: # If no API key or token is invalid, run login if not api_key or not await is_token_valid(server_url, api_key): if not api_key: - _console.print( - f"[{OPENHANDS_THEME.warning}]You are not logged in to OpenHands Cloud." - f"[/{OPENHANDS_THEME.warning}]" + console_print( + "You are not logged in to OpenHands Cloud.", + style=OPENHANDS_THEME.warning, ) else: - _console.print( - f"[{OPENHANDS_THEME.warning}]Your connection with OpenHands Cloud " - f"has expired.[/{OPENHANDS_THEME.warning}]" + console_print( + "Your connection with OpenHands Cloud has expired.", + style=OPENHANDS_THEME.warning, ) - _console.print( - f"[{OPENHANDS_THEME.accent}]Starting login...[/{OPENHANDS_THEME.accent}]" - ) + console_print("Starting login...", style=OPENHANDS_THEME.accent) success = await login_command(server_url) if not success: raise AuthenticationError("Login failed") diff --git a/openhands_cli/cloud/command.py b/openhands_cli/cloud/command.py index 0da65a9d..34219475 100644 --- a/openhands_cli/cloud/command.py +++ b/openhands_cli/cloud/command.py @@ -4,9 +4,11 @@ import asyncio import sys -from rich.console import Console - -from openhands_cli.auth.utils import AuthenticationError, ensure_valid_auth +from openhands_cli.auth.utils import ( + AuthenticationError, + console_print, + ensure_valid_auth, +) from openhands_cli.cloud.conversation import ( CloudConversationError, create_cloud_conversation, @@ -15,9 +17,6 @@ from openhands_cli.utils import create_seeded_instructions_from_args -console = Console() - - async def _run_cloud_conversation(server_url: str, initial_message: str) -> None: """Run cloud conversation with authentication.""" api_key = await ensure_valid_auth(server_url) @@ -41,14 +40,13 @@ def handle_cloud_command(args: argparse.Namespace) -> None: # Get the initial message from args queued_inputs = create_seeded_instructions_from_args(args) if not queued_inputs: - console.print( - f"[{OPENHANDS_THEME.error}]Error: No initial message " - f"provided for cloud conversation." - f"[/{OPENHANDS_THEME.error}]" + console_print( + "Error: No initial message provided for cloud conversation.", + style=OPENHANDS_THEME.error, ) - console.print( - f"[{OPENHANDS_THEME.secondary}]Use --task or --file to " - f"provide an initial message.[/{OPENHANDS_THEME.secondary}]" + console_print( + "Use --task or --file to provide an initial message.", + style=OPENHANDS_THEME.secondary, ) return @@ -57,17 +55,14 @@ def handle_cloud_command(args: argparse.Namespace) -> None: # Ensure authentication and create cloud conversation asyncio.run(_run_cloud_conversation(args.server_url, initial_message)) - console.print( - f"[{OPENHANDS_THEME.success}]Cloud conversation created " - f"successfully! 🚀[/{OPENHANDS_THEME.success}]" + console_print( + "Cloud conversation created successfully! 🚀", + style=OPENHANDS_THEME.success, ) except (CloudConversationError, AuthenticationError): # Error already printed in the function sys.exit(1) except Exception as e: - console.print( - f"[{OPENHANDS_THEME.error}]Unexpected error: " - f"{str(e)}[/{OPENHANDS_THEME.error}]" - ) + console_print(f"Unexpected error: {e}", style=OPENHANDS_THEME.error) sys.exit(1) diff --git a/openhands_cli/cloud/conversation.py b/openhands_cli/cloud/conversation.py index c6c5be7d..39344e89 100644 --- a/openhands_cli/cloud/conversation.py +++ b/openhands_cli/cloud/conversation.py @@ -17,16 +17,13 @@ import subprocess from typing import Any -from rich.console import Console - from openhands_cli.auth.api_client import OpenHandsApiClient +from openhands_cli.auth.utils import console_print from openhands_cli.theme import OPENHANDS_THEME logger = logging.getLogger(__name__) -console = Console() - class CloudConversationError(Exception): """Exception raised for cloud conversation errors.""" @@ -49,17 +46,16 @@ async def create_cloud_conversation( client = OpenHandsApiClient(server_url, api_key) repo, branch = extract_repository_from_cwd() + accent = OPENHANDS_THEME.accent if repo: - console.print( - f"[{OPENHANDS_THEME.secondary}]Detected repository: " - f"[{OPENHANDS_THEME.accent}]{repo}[/{OPENHANDS_THEME.accent}]" - f"[/{OPENHANDS_THEME.secondary}]" + console_print( + f"Detected repository: [{accent}]{repo}[/{accent}]", + style=OPENHANDS_THEME.secondary, ) if branch: - console.print( - f"[{OPENHANDS_THEME.secondary}]Detected branch: " - f"[{OPENHANDS_THEME.accent}]{branch}[/{OPENHANDS_THEME.accent}]" - f"[/{OPENHANDS_THEME.secondary}]" + console_print( + f"Detected branch: [{accent}]{branch}[/{accent}]", + style=OPENHANDS_THEME.secondary, ) payload: dict[str, Any] = {"initial_user_msg": initial_user_msg} @@ -68,11 +64,7 @@ async def create_cloud_conversation( if branch: payload["selected_branch"] = branch - console.print( - f"[{OPENHANDS_THEME.accent}]" - "Creating cloud conversation..." - f"[/{OPENHANDS_THEME.accent}]" - ) + console_print("Creating cloud conversation...", style=OPENHANDS_THEME.accent) try: resp = await client.create_conversation(json_data=payload) @@ -80,25 +72,22 @@ async def create_cloud_conversation( except CloudConversationError: raise except Exception as e: - console.print( - f"[{OPENHANDS_THEME.error}]Error creating cloud conversation: {e}" - f"[/{OPENHANDS_THEME.error}]" + console_print( + f"Error creating cloud conversation: {e}", style=OPENHANDS_THEME.error ) raise CloudConversationError(f"Failed to create conversation: {e}") from e conversation_id = conversation.get("conversation_id") - console.print( - f"[{OPENHANDS_THEME.secondary}]Conversation ID: " - f"[{OPENHANDS_THEME.accent}]{conversation_id}[/{OPENHANDS_THEME.accent}]" - f"[/{OPENHANDS_THEME.secondary}]" + console_print( + f"Conversation ID: [{accent}]{conversation_id}[/{accent}]", + style=OPENHANDS_THEME.secondary, ) if conversation_id: url = f"{server_url}/conversations/{conversation_id}" - console.print( - f"[{OPENHANDS_THEME.secondary}]View in browser: " - f"[{OPENHANDS_THEME.accent}]{url}[/{OPENHANDS_THEME.accent}]" - f"[/{OPENHANDS_THEME.secondary}]" + console_print( + f"View in browser: [{accent}]{url}[/{accent}]", + style=OPENHANDS_THEME.secondary, ) return conversation From d03de2808b477f4d3233504a1a620de705af5dbc Mon Sep 17 00:00:00 2001 From: vasco Date: Sat, 7 Mar 2026 10:28:30 +0100 Subject: [PATCH 4/4] Revert "refactoring" This reverts commit 0c76fe011c2e52a9d494d7ba2269ee7835e2ae84. --- openhands_cli/auth/api_client.py | 135 +++++++++++++++++++-------- openhands_cli/auth/device_flow.py | 43 +++++---- openhands_cli/auth/login_command.py | 50 ++++++---- openhands_cli/auth/logout_command.py | 25 +++-- openhands_cli/auth/utils.py | 32 +++---- openhands_cli/cloud/command.py | 35 ++++--- openhands_cli/cloud/conversation.py | 45 +++++---- 7 files changed, 227 insertions(+), 138 deletions(-) diff --git a/openhands_cli/auth/api_client.py b/openhands_cli/auth/api_client.py index 91143906..5044ee73 100644 --- a/openhands_cli/auth/api_client.py +++ b/openhands_cli/auth/api_client.py @@ -111,26 +111,38 @@ async def get_conversation_info( def _print_settings_summary(settings: dict[str, Any]) -> None: - console_print(" ✓ User settings retrieved", style=OPENHANDS_THEME.success) + console_print( + f"[{OPENHANDS_THEME.success}] ✓ User " + f"settings retrieved[/{OPENHANDS_THEME.success}]" + ) llm_model = settings.get("llm_model", "Not set") agent_name = settings.get("agent", "Not set") language = settings.get("language", "Not set") llm_api_key_set = settings.get("llm_api_key_set", False) - console_print(f" LLM Model: {llm_model}", style=OPENHANDS_THEME.secondary) - console_print(f" Agent: {agent_name}", style=OPENHANDS_THEME.secondary) - console_print(f" Language: {language}", style=OPENHANDS_THEME.secondary) + console_print( + f" [{OPENHANDS_THEME.secondary}]LLM Model: " + f"{llm_model}[/{OPENHANDS_THEME.secondary}]" + ) + console_print( + f" [{OPENHANDS_THEME.secondary}]Agent: " + f"{agent_name}[/{OPENHANDS_THEME.secondary}]" + ) + console_print( + f" [{OPENHANDS_THEME.secondary}]Language: " + f"{language}[/{OPENHANDS_THEME.secondary}]" + ) if llm_api_key_set: console_print( - " ✓ LLM API key is configured in settings", - style=OPENHANDS_THEME.success, + f" [{OPENHANDS_THEME.success}]✓ LLM API key is configured in " + f"settings[/{OPENHANDS_THEME.success}]" ) else: console_print( - " ! No LLM API key configured in settings", - style=OPENHANDS_THEME.warning, + f" [{OPENHANDS_THEME.warning}]! No LLM API key configured in " + f"settings[/{OPENHANDS_THEME.warning}]" ) @@ -151,12 +163,12 @@ def _ask_user_consent_for_overwrite( True if user consents to overwrite, False otherwise """ console_print( - "\n⚠️ Existing agent configuration found!", style=OPENHANDS_THEME.warning + f"\n[{OPENHANDS_THEME.warning}]⚠️ Existing agent configuration found!" + f"[/{OPENHANDS_THEME.warning}]" ) console_print( - "This will overwrite your current settings with " - "the ones from OpenHands Cloud.\n", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.secondary}]This will overwrite your current settings with " + f"the ones from OpenHands Cloud.[/{OPENHANDS_THEME.secondary}]\n" ) # Show current vs new settings comparison @@ -164,7 +176,10 @@ def _ask_user_consent_for_overwrite( new_model = new_settings.get("llm_model", default_model) base_url = new_settings.get("llm_base_url", None) - console_print("Current configuration:", style=OPENHANDS_THEME.secondary) + console_print( + f"[{OPENHANDS_THEME.secondary}]Current " + f"configuration:[/{OPENHANDS_THEME.secondary}]" + ) console_print( f" • Model: [{OPENHANDS_THEME.accent}]{html.escape(current_model)}" f"[/{OPENHANDS_THEME.accent}]" @@ -176,7 +191,10 @@ def _ask_user_consent_for_overwrite( f"{html.escape(existing_agent.llm.base_url)}[/{OPENHANDS_THEME.accent}]" ) - console_print("\nNew configuration from cloud:", style=OPENHANDS_THEME.secondary) + console_print( + f"\n[{OPENHANDS_THEME.secondary}]New configuration from " + f"cloud:[/{OPENHANDS_THEME.secondary}]" + ) console_print( f" • Model: [{OPENHANDS_THEME.accent}]{html.escape(new_model)}" f"[/{OPENHANDS_THEME.accent}]" @@ -232,32 +250,49 @@ def create_and_save_agent_configuration( ) console_print( - "✓ Agent configuration created and saved!", style=OPENHANDS_THEME.success + f"[{OPENHANDS_THEME.success}]✓ Agent configuration created and " + f"saved![/{OPENHANDS_THEME.success}]" + ) + console_print( + f"[{OPENHANDS_THEME.secondary}]Configuration " + f"details:[/{OPENHANDS_THEME.secondary}]" ) - console_print("Configuration details:", style=OPENHANDS_THEME.secondary) llm = agent.llm - console_print(f" • Model: {llm.model}", style=OPENHANDS_THEME.accent) - console_print(f" • Base URL: {llm.base_url}", style=OPENHANDS_THEME.accent) - console_print(f" • Usage ID: {llm.usage_id}", style=OPENHANDS_THEME.accent) - console_print(" • API Key: ✓ Set", style=OPENHANDS_THEME.accent) + console_print( + f" • Model: [{OPENHANDS_THEME.accent}]{llm.model}[/{OPENHANDS_THEME.accent}]" + ) + console_print( + f" • Base URL: [{OPENHANDS_THEME.accent}]{llm.base_url}" + f"[/{OPENHANDS_THEME.accent}]" + ) + console_print( + f" • Usage ID: [{OPENHANDS_THEME.accent}]{llm.usage_id}" + f"[/{OPENHANDS_THEME.accent}]" + ) + console_print( + f" • API Key: [{OPENHANDS_THEME.accent}]✓ Set[/{OPENHANDS_THEME.accent}]" + ) tools_count = len(agent.tools) console_print( - f" • Tools: {tools_count} default tools loaded", style=OPENHANDS_THEME.accent + f" • Tools: [{OPENHANDS_THEME.accent}]{tools_count} default tools loaded" + f"[/{OPENHANDS_THEME.accent}]" ) condenser = agent.condenser if isinstance(condenser, LLMSummarizingCondenser): console_print( - f" • Condenser: LLM Summarizing " + f" • Condenser: [{OPENHANDS_THEME.accent}]LLM Summarizing " f"(max_size: {condenser.max_size}, " - f"keep_first: {condenser.keep_first})", - style=OPENHANDS_THEME.accent, + f"keep_first: {condenser.keep_first})[/{OPENHANDS_THEME.accent}]" ) - console_print(f" • Saved to: {get_settings_path()}", style=OPENHANDS_THEME.accent) + console_print( + f" • Saved to: [{OPENHANDS_THEME.accent}]{get_settings_path()}" + f"[/{OPENHANDS_THEME.accent}]" + ) async def fetch_user_data_after_oauth( @@ -267,29 +302,41 @@ async def fetch_user_data_after_oauth( """Fetch user data after OAuth and optionally create & save an Agent.""" client = OpenHandsApiClient(server_url, api_key) - console_print("Fetching user data...", style=OPENHANDS_THEME.accent) + console_print( + f"[{OPENHANDS_THEME.accent}]Fetching user data...[/{OPENHANDS_THEME.accent}]" + ) try: # Fetch LLM API key - console_print("• Getting LLM API key...", style=OPENHANDS_THEME.secondary) + console_print( + f"[{OPENHANDS_THEME.secondary}]• Getting LLM API key..." + f"[/{OPENHANDS_THEME.secondary}]" + ) llm_api_key = await client.get_llm_api_key() if llm_api_key: console_print( - f" ✓ LLM API key retrieved: {llm_api_key[:3]}...", - style=OPENHANDS_THEME.success, + f"[{OPENHANDS_THEME.success}] ✓ LLM API key retrieved: " + f"{llm_api_key[:3]}...[/{OPENHANDS_THEME.success}]" ) else: - console_print(" ! No LLM API key available", style=OPENHANDS_THEME.warning) + console_print( + f"[{OPENHANDS_THEME.warning}] ! No " + f"LLM API key available[/{OPENHANDS_THEME.warning}]" + ) # Fetch user settings - console_print("• Getting user settings...", style=OPENHANDS_THEME.secondary) + console_print( + f"[{OPENHANDS_THEME.secondary}]• Getting user settings..." + f"[/{OPENHANDS_THEME.secondary}]" + ) settings = await client.get_user_settings() if settings: _print_settings_summary(settings) else: console_print( - " ! No user settings available", style=OPENHANDS_THEME.warning + f"[{OPENHANDS_THEME.warning}] ! No " + f"user settings available[/{OPENHANDS_THEME.warning}]" ) user_data = { @@ -304,27 +351,33 @@ async def fetch_user_data_after_oauth( except ValueError as e: # User declined to overwrite existing configuration console_print("\n") - console_print(str(e), style=OPENHANDS_THEME.warning) console_print( - "Keeping existing agent configuration.", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.warning}]{e}[/{OPENHANDS_THEME.warning}]" + ) + console_print( + f"[{OPENHANDS_THEME.secondary}]Keeping existing " + f"agent configuration.[/{OPENHANDS_THEME.secondary}]" ) except Exception as e: console_print( - f"Warning: Could not create agent configuration: {e}", - style=OPENHANDS_THEME.warning, + f"[{OPENHANDS_THEME.warning}]Warning: Could not create " + f"agent configuration: {e}[/{OPENHANDS_THEME.warning}]" ) else: console_print( - "Skipping agent configuration; missing key or settings.", - style=OPENHANDS_THEME.warning, + f"[{OPENHANDS_THEME.warning}]Skipping agent configuration; " + f"missing key or settings.[/{OPENHANDS_THEME.warning}]" ) console_print( - "✓ User data fetched successfully!", style=OPENHANDS_THEME.success + f"[{OPENHANDS_THEME.success}]✓ User data " + f"fetched successfully![/{OPENHANDS_THEME.success}]" ) return user_data except ApiClientError as e: - console_print(f"Error fetching user data: {e}", style=OPENHANDS_THEME.error) + console_print( + f"[{OPENHANDS_THEME.error}]Error fetching user data: " + f"{e}[/{OPENHANDS_THEME.error}]" + ) raise diff --git a/openhands_cli/auth/device_flow.py b/openhands_cli/auth/device_flow.py index 59cf6245..f2145f29 100644 --- a/openhands_cli/auth/device_flow.py +++ b/openhands_cli/auth/device_flow.py @@ -161,51 +161,55 @@ async def authenticate(self) -> DeviceTokenResponse: DeviceFlowError: If authentication fails """ console_print( - "Starting OpenHands authentication...", style=OPENHANDS_THEME.accent + f"[{OPENHANDS_THEME.accent}]Starting OpenHands authentication..." + f"[/{OPENHANDS_THEME.accent}]" ) # Step 1: Start device flow try: auth_response = await self.start_device_flow() except DeviceFlowError as e: - console_print(f"Error: {e}", style=OPENHANDS_THEME.error) + console_print( + f"[{OPENHANDS_THEME.error}]Error: {e}[/{OPENHANDS_THEME.error}]" + ) raise # Step 2: Use verification_uri_complete if available, otherwise construct URL verification_url = auth_response.verification_uri_complete console_print( - "\nOpening your web browser for authentication...", - style=OPENHANDS_THEME.warning, + f"\n[{OPENHANDS_THEME.warning}]Opening your web browser for " + f"authentication...[/{OPENHANDS_THEME.warning}]" ) console_print( - f"URL: [bold]{verification_url}[/bold]", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.secondary}]URL: [bold]{verification_url}[/bold]" + f"[/{OPENHANDS_THEME.secondary}]" ) # Automatically open the browser try: webbrowser.open(verification_url) console_print( - "✓ Browser opened successfully", style=OPENHANDS_THEME.success + f"[{OPENHANDS_THEME.success}]✓ Browser " + f"opened successfully[/{OPENHANDS_THEME.success}]" ) except Exception as e: console_print( - f"Could not open browser automatically: {e}", - style=OPENHANDS_THEME.warning, + f"[{OPENHANDS_THEME.warning}]Could not open browser automatically: " + f"{e}[/{OPENHANDS_THEME.warning}]" ) console_print( - f"Please manually open: [bold]{verification_url}[/bold]", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.secondary}]Please manually open: " + f"[bold]{verification_url}[/bold][/{OPENHANDS_THEME.secondary}]" ) console_print( - "Follow the instructions in your browser to complete authentication", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.secondary}]Follow the instructions in your browser " + f"to complete authentication[/{OPENHANDS_THEME.secondary}]" ) console_print( - "\nWaiting for authentication to complete...", - style=OPENHANDS_THEME.accent, + f"\n[{OPENHANDS_THEME.accent}]Waiting for authentication to complete..." + f"[/{OPENHANDS_THEME.accent}]" ) # Step 3: Poll for token using device_code and interval from auth_response @@ -213,10 +217,15 @@ async def authenticate(self) -> DeviceTokenResponse: token_response = await self.poll_for_token( auth_response.device_code, auth_response.interval ) - console_print("✓ Authentication successful!", style=OPENHANDS_THEME.success) + console_print( + f"[{OPENHANDS_THEME.success}]✓ Authentication " + f"successful![/{OPENHANDS_THEME.success}]" + ) return token_response except DeviceFlowError as e: - console_print(f"Error: {e}", style=OPENHANDS_THEME.error) + console_print( + f"[{OPENHANDS_THEME.error}]Error: {e}[/{OPENHANDS_THEME.error}]" + ) raise diff --git a/openhands_cli/auth/login_command.py b/openhands_cli/auth/login_command.py index 1c120bc4..83497efa 100644 --- a/openhands_cli/auth/login_command.py +++ b/openhands_cli/auth/login_command.py @@ -24,12 +24,12 @@ async def _fetch_user_data_with_context( # Initial context output if already_logged_in: console_print( - "You are already logged in to OpenHands Cloud.", - style=OPENHANDS_THEME.warning, + f"[{OPENHANDS_THEME.warning}]You are already logged in to " + f"OpenHands Cloud.[/{OPENHANDS_THEME.warning}]" ) console_print( - "Pulling latest settings from remote...", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.secondary}]Pulling latest settings from remote..." + f"[/{OPENHANDS_THEME.secondary}]" ) # If already logged, skip re-fetching settings @@ -41,8 +41,8 @@ async def _fetch_user_data_with_context( # --- SUCCESS MESSAGES --- console_print( - "\n✓ Settings synchronized successfully!", - style=OPENHANDS_THEME.success, + f"\n[{OPENHANDS_THEME.success}]✓ Settings synchronized " + f"successfully![/{OPENHANDS_THEME.success}]" ) except ApiClientError as e: @@ -50,13 +50,13 @@ async def _fetch_user_data_with_context( safe_error = html.escape(str(e)) console_print( - f"\nWarning: Could not fetch user data: {safe_error}", - style=OPENHANDS_THEME.warning, + f"\n[{OPENHANDS_THEME.warning}]Warning: " + f"Could not fetch user data: {safe_error}[/{OPENHANDS_THEME.warning}]" ) - escaped_cmd = html.escape("openhands logout && openhands login") console_print( - f"Please try: [bold]{escaped_cmd}[/bold]", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.secondary}]Please try: [bold]" + f"{html.escape('openhands logout && openhands login')}" + f"[/bold][/{OPENHANDS_THEME.secondary}]" ) @@ -77,13 +77,16 @@ async def login_command(server_url: str, skip_settings_sync: bool = False) -> bo if existing_api_key and not await is_token_valid(server_url, existing_api_key): console_print( - "Token is invalid or expired. Logging out...", - style=OPENHANDS_THEME.warning, + f"[{OPENHANDS_THEME.warning}]Token is invalid or expired. " + f"Logging out...[/{OPENHANDS_THEME.warning}]" ) logout_command(server_url) # Proceed with normal login flow - console_print("Logging in to OpenHands Cloud...", style=OPENHANDS_THEME.accent) + console_print( + f"[{OPENHANDS_THEME.accent}]Logging in to OpenHands Cloud..." + f"[/{OPENHANDS_THEME.accent}]" + ) # Re-read token (may have been cleared by logout above) existing_api_key = token_storage.get_api_key() @@ -102,7 +105,10 @@ async def login_command(server_url: str, skip_settings_sync: bool = False) -> bo try: token_response = await authenticate_with_device_flow(server_url) except DeviceFlowError as e: - console_print(f"Authentication failed: {e}", style=OPENHANDS_THEME.error) + console_print( + f"[{OPENHANDS_THEME.error}]Authentication failed: " + f"{e}[/{OPENHANDS_THEME.error}]" + ) return False api_key = token_response.access_token @@ -110,10 +116,13 @@ async def login_command(server_url: str, skip_settings_sync: bool = False) -> bo # Store the API key securely token_storage.store_api_key(api_key) - console_print("✓ Logged into OpenHands Cloud", style=OPENHANDS_THEME.success) console_print( - "Your authentication tokens have been stored securely.", - style=OPENHANDS_THEME.secondary, + f"[{OPENHANDS_THEME.success}]✓ Logged " + f"into OpenHands Cloud[/{OPENHANDS_THEME.success}]" + ) + console_print( + f"[{OPENHANDS_THEME.secondary}]Your authentication " + f"tokens have been stored securely.[/{OPENHANDS_THEME.secondary}]" ) # Fetch user data and configure local agent @@ -138,5 +147,8 @@ def run_login_command(server_url: str) -> bool: try: return asyncio.run(login_command(server_url)) except KeyboardInterrupt: - console_print("\nLogin cancelled by user.", style=OPENHANDS_THEME.warning) + console_print( + f"\n[{OPENHANDS_THEME.warning}]Login cancelled by " + f"user.[/{OPENHANDS_THEME.warning}]" + ) return False diff --git a/openhands_cli/auth/logout_command.py b/openhands_cli/auth/logout_command.py index a6bff97f..0959a0e8 100644 --- a/openhands_cli/auth/logout_command.py +++ b/openhands_cli/auth/logout_command.py @@ -20,18 +20,20 @@ def logout_command(server_url: str | None = None) -> bool: # Logging out from a specific server (conceptually; we only store one key) if server_url: console_print( - "Logging out from OpenHands Cloud...", style=OPENHANDS_THEME.accent + f"[{OPENHANDS_THEME.accent}]Logging out from OpenHands Cloud..." + f"[/{OPENHANDS_THEME.accent}]" ) was_logged_in = token_storage.remove_api_key() if was_logged_in: console_print( - "✓ Logged out of OpenHands Cloud", style=OPENHANDS_THEME.success + f"[{OPENHANDS_THEME.success}]✓ Logged " + f"out of OpenHands Cloud[/{OPENHANDS_THEME.success}]" ) else: console_print( - "You were not logged in to OpenHands Cloud", - style=OPENHANDS_THEME.warning, + f"[{OPENHANDS_THEME.warning}]You were not logged in to " + f"OpenHands Cloud[/{OPENHANDS_THEME.warning}]" ) return True @@ -39,21 +41,26 @@ def logout_command(server_url: str | None = None) -> bool: # Logging out globally (no server specified) if not token_storage.has_api_key(): console_print( - "You are not logged in to OpenHands Cloud.", - style=OPENHANDS_THEME.warning, + f"[{OPENHANDS_THEME.warning}]You are not logged in to " + f"OpenHands Cloud.[/{OPENHANDS_THEME.warning}]" ) return True console_print( - "Logging out from OpenHands Cloud...", style=OPENHANDS_THEME.accent + f"[{OPENHANDS_THEME.accent}]Logging out from OpenHands Cloud..." + f"[/{OPENHANDS_THEME.accent}]" ) token_storage.remove_api_key() - console_print("✓ Logged out of OpenHands Cloud", style=OPENHANDS_THEME.success) + console_print( + f"[{OPENHANDS_THEME.success}]✓ Logged " + f"out of OpenHands Cloud[/{OPENHANDS_THEME.success}]" + ) return True except Exception as e: console_print( - f"Unexpected error during logout: {e}", style=OPENHANDS_THEME.error + f"[{OPENHANDS_THEME.error}]Unexpected error during logout: " + f"{e}[/{OPENHANDS_THEME.error}]" ) return False diff --git a/openhands_cli/auth/utils.py b/openhands_cli/auth/utils.py index 27fb24b7..2f306595 100644 --- a/openhands_cli/auth/utils.py +++ b/openhands_cli/auth/utils.py @@ -16,19 +16,9 @@ _console = Console() -def console_print(message: str, *, style: str | None = None) -> None: - """Unified formatted print helper using rich console. - - Args: - message: Text to print (may contain Rich markup). - style: Optional OPENHANDS_THEME style name. When given, the message - is automatically wrapped in ``[{style}]…[/{style}]`` tags so - callers don't have to repeat the verbose markup pattern. - """ - if style: - _console.print(f"[{style}]{message}[/{style}]") - else: - _console.print(message) +def console_print(message: str) -> None: + """Unified formatted print helper using rich console.""" + _console.print(message) async def is_token_valid(server_url: str, api_key: str) -> bool: @@ -69,17 +59,19 @@ async def ensure_valid_auth(server_url: str) -> str: # If no API key or token is invalid, run login if not api_key or not await is_token_valid(server_url, api_key): if not api_key: - console_print( - "You are not logged in to OpenHands Cloud.", - style=OPENHANDS_THEME.warning, + _console.print( + f"[{OPENHANDS_THEME.warning}]You are not logged in to OpenHands Cloud." + f"[/{OPENHANDS_THEME.warning}]" ) else: - console_print( - "Your connection with OpenHands Cloud has expired.", - style=OPENHANDS_THEME.warning, + _console.print( + f"[{OPENHANDS_THEME.warning}]Your connection with OpenHands Cloud " + f"has expired.[/{OPENHANDS_THEME.warning}]" ) - console_print("Starting login...", style=OPENHANDS_THEME.accent) + _console.print( + f"[{OPENHANDS_THEME.accent}]Starting login...[/{OPENHANDS_THEME.accent}]" + ) success = await login_command(server_url) if not success: raise AuthenticationError("Login failed") diff --git a/openhands_cli/cloud/command.py b/openhands_cli/cloud/command.py index 34219475..0da65a9d 100644 --- a/openhands_cli/cloud/command.py +++ b/openhands_cli/cloud/command.py @@ -4,11 +4,9 @@ import asyncio import sys -from openhands_cli.auth.utils import ( - AuthenticationError, - console_print, - ensure_valid_auth, -) +from rich.console import Console + +from openhands_cli.auth.utils import AuthenticationError, ensure_valid_auth from openhands_cli.cloud.conversation import ( CloudConversationError, create_cloud_conversation, @@ -17,6 +15,9 @@ from openhands_cli.utils import create_seeded_instructions_from_args +console = Console() + + async def _run_cloud_conversation(server_url: str, initial_message: str) -> None: """Run cloud conversation with authentication.""" api_key = await ensure_valid_auth(server_url) @@ -40,13 +41,14 @@ def handle_cloud_command(args: argparse.Namespace) -> None: # Get the initial message from args queued_inputs = create_seeded_instructions_from_args(args) if not queued_inputs: - console_print( - "Error: No initial message provided for cloud conversation.", - style=OPENHANDS_THEME.error, + console.print( + f"[{OPENHANDS_THEME.error}]Error: No initial message " + f"provided for cloud conversation." + f"[/{OPENHANDS_THEME.error}]" ) - console_print( - "Use --task or --file to provide an initial message.", - style=OPENHANDS_THEME.secondary, + console.print( + f"[{OPENHANDS_THEME.secondary}]Use --task or --file to " + f"provide an initial message.[/{OPENHANDS_THEME.secondary}]" ) return @@ -55,14 +57,17 @@ def handle_cloud_command(args: argparse.Namespace) -> None: # Ensure authentication and create cloud conversation asyncio.run(_run_cloud_conversation(args.server_url, initial_message)) - console_print( - "Cloud conversation created successfully! 🚀", - style=OPENHANDS_THEME.success, + console.print( + f"[{OPENHANDS_THEME.success}]Cloud conversation created " + f"successfully! 🚀[/{OPENHANDS_THEME.success}]" ) except (CloudConversationError, AuthenticationError): # Error already printed in the function sys.exit(1) except Exception as e: - console_print(f"Unexpected error: {e}", style=OPENHANDS_THEME.error) + console.print( + f"[{OPENHANDS_THEME.error}]Unexpected error: " + f"{str(e)}[/{OPENHANDS_THEME.error}]" + ) sys.exit(1) diff --git a/openhands_cli/cloud/conversation.py b/openhands_cli/cloud/conversation.py index 39344e89..c6c5be7d 100644 --- a/openhands_cli/cloud/conversation.py +++ b/openhands_cli/cloud/conversation.py @@ -17,13 +17,16 @@ import subprocess from typing import Any +from rich.console import Console + from openhands_cli.auth.api_client import OpenHandsApiClient -from openhands_cli.auth.utils import console_print from openhands_cli.theme import OPENHANDS_THEME logger = logging.getLogger(__name__) +console = Console() + class CloudConversationError(Exception): """Exception raised for cloud conversation errors.""" @@ -46,16 +49,17 @@ async def create_cloud_conversation( client = OpenHandsApiClient(server_url, api_key) repo, branch = extract_repository_from_cwd() - accent = OPENHANDS_THEME.accent if repo: - console_print( - f"Detected repository: [{accent}]{repo}[/{accent}]", - style=OPENHANDS_THEME.secondary, + console.print( + f"[{OPENHANDS_THEME.secondary}]Detected repository: " + f"[{OPENHANDS_THEME.accent}]{repo}[/{OPENHANDS_THEME.accent}]" + f"[/{OPENHANDS_THEME.secondary}]" ) if branch: - console_print( - f"Detected branch: [{accent}]{branch}[/{accent}]", - style=OPENHANDS_THEME.secondary, + console.print( + f"[{OPENHANDS_THEME.secondary}]Detected branch: " + f"[{OPENHANDS_THEME.accent}]{branch}[/{OPENHANDS_THEME.accent}]" + f"[/{OPENHANDS_THEME.secondary}]" ) payload: dict[str, Any] = {"initial_user_msg": initial_user_msg} @@ -64,7 +68,11 @@ async def create_cloud_conversation( if branch: payload["selected_branch"] = branch - console_print("Creating cloud conversation...", style=OPENHANDS_THEME.accent) + console.print( + f"[{OPENHANDS_THEME.accent}]" + "Creating cloud conversation..." + f"[/{OPENHANDS_THEME.accent}]" + ) try: resp = await client.create_conversation(json_data=payload) @@ -72,22 +80,25 @@ async def create_cloud_conversation( except CloudConversationError: raise except Exception as e: - console_print( - f"Error creating cloud conversation: {e}", style=OPENHANDS_THEME.error + console.print( + f"[{OPENHANDS_THEME.error}]Error creating cloud conversation: {e}" + f"[/{OPENHANDS_THEME.error}]" ) raise CloudConversationError(f"Failed to create conversation: {e}") from e conversation_id = conversation.get("conversation_id") - console_print( - f"Conversation ID: [{accent}]{conversation_id}[/{accent}]", - style=OPENHANDS_THEME.secondary, + console.print( + f"[{OPENHANDS_THEME.secondary}]Conversation ID: " + f"[{OPENHANDS_THEME.accent}]{conversation_id}[/{OPENHANDS_THEME.accent}]" + f"[/{OPENHANDS_THEME.secondary}]" ) if conversation_id: url = f"{server_url}/conversations/{conversation_id}" - console_print( - f"View in browser: [{accent}]{url}[/{accent}]", - style=OPENHANDS_THEME.secondary, + console.print( + f"[{OPENHANDS_THEME.secondary}]View in browser: " + f"[{OPENHANDS_THEME.accent}]{url}[/{OPENHANDS_THEME.accent}]" + f"[/{OPENHANDS_THEME.secondary}]" ) return conversation