From 6889d8673b44554e879034079e12cfe91a14b7be Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Thu, 10 Jul 2025 14:52:25 -0700 Subject: [PATCH] `--debug` for CLI commands (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` (base) ➜ arcade-ai git:(debug) ✗ uv run arcade busted-command --host invalid-host ❌ Login failed with error: Not implemented ^C Aborted. (base) ➜ arcade-ai git:(debug) ✗ uv run arcade busted-command --host invalid-host --debug ❌ Login failed with error: Traceback (most recent call last): File "/Users/evan/workspace/arcade/Team/arcade-ai/libs/arcade-cli/arcade_cli/main.py", line 106, in login raise Exception("Not implemented") Exception: Not implemented ``` --- libs/arcade-cli/arcade_cli/main.py | 107 ++++++++++++++++++----------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/libs/arcade-cli/arcade_cli/main.py b/libs/arcade-cli/arcade_cli/main.py index a2e37119..f7ab9b56 100644 --- a/libs/arcade-cli/arcade_cli/main.py +++ b/libs/arcade-cli/arcade_cli/main.py @@ -1,6 +1,7 @@ import asyncio import os import threading +import traceback import uuid import webbrowser from pathlib import Path @@ -69,6 +70,21 @@ cli.add_typer( console = Console() +def handle_cli_error( + message: str, error: Exception | None = None, debug: bool = True, should_exit: bool = True +) -> None: + """Handle CLI error reporting with optional debug traceback and exit.""" + if error and debug: + console.print(f"❌ {message}: {traceback.format_exc()}", style="bold red") + elif error: + console.print(f"❌ {message}: {escape(str(error))}", style="bold red") + else: + console.print(f"❌ {message}", style="bold red") + + if should_exit: + raise typer.Exit(code=1) + + @cli.command(help="Log in to Arcade Cloud", rich_help_panel="User") def login( host: str = typer.Option( @@ -83,6 +99,7 @@ def login( "--port", help="The port of the Arcade Cloud host (if running locally).", ), + debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"), ) -> None: """ Logs the user into Arcade Cloud. @@ -115,22 +132,29 @@ def login( server_thread.join() except KeyboardInterrupt: auth_server.shutdown_server() + except Exception as e: + handle_cli_error("Login failed", e, debug) finally: if server_thread.is_alive(): server_thread.join() # Ensure the server thread completes and cleans up @cli.command(help="Log out of Arcade Cloud", rich_help_panel="User") -def logout() -> None: +def logout( + debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"), +) -> None: """ Logs the user out of Arcade Cloud. """ - # If the credentials file exists, delete it - if os.path.exists(CREDENTIALS_FILE_PATH): - os.remove(CREDENTIALS_FILE_PATH) - console.print("You're now logged out.", style="bold") - else: - console.print("You're not logged in.", style="bold red") + try: + # If the credentials file exists, delete it + if os.path.exists(CREDENTIALS_FILE_PATH): + os.remove(CREDENTIALS_FILE_PATH) + console.print("You're now logged out.", style="bold") + else: + console.print("You're not logged in.", style="bold red") + except Exception as e: + handle_cli_error("Logout failed", e, debug) @cli.command( @@ -143,6 +167,7 @@ def new( metavar="TOOLKIT_NAME", ), directory: str = typer.Option(os.getcwd(), "--dir", help="tools directory path"), + debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"), ) -> None: """ Creates a new toolkit with the given name, description, and result type. @@ -152,8 +177,7 @@ def new( try: create_new_toolkit(directory, toolkit_name) except Exception as e: - error_message = f"❌ Failed to create new Toolkit: {escape(str(e))}" - console.print(error_message, style="bold red") + handle_cli_error("Failed to create new Toolkit", e, debug) @cli.command( @@ -318,7 +342,7 @@ def chat( tool_messages = chat_result.tool_messages except OpenAIError as e: - console.print(f"❌ Arcade Chat failed with error: {e!s}", style="bold red") + handle_cli_error("Arcade Chat failed", e, debug, should_exit=False) continue if debug: display_tool_messages(tool_messages) @@ -328,9 +352,7 @@ def chat( typer.Exit() except RuntimeError as e: - error_message = f"❌ Failed to run tool{': ' + escape(str(e)) if str(e) else ''}" - console.print(error_message, style="bold red") - raise typer.Exit() + handle_cli_error("Failed to run tool", e, debug) @cli.command(help="Run tool calling evaluations", rich_help_panel="Tool Development") @@ -376,6 +398,7 @@ def evals( "--no-tls", help="Whether to disable TLS for the connection to the Arcade Engine.", ), + debug: bool = typer.Option(False, "--debug", help="Show debug information"), ) -> None: """ Find all files starting with 'eval_' in the given directory, @@ -463,7 +486,10 @@ def evals( all_evaluations.extend(results) display_eval_results(all_evaluations, show_details=show_details) - asyncio.run(run_evaluations()) + try: + asyncio.run(run_evaluations()) + except Exception as e: + handle_cli_error("Failed to run evaluations", e, debug) @cli.command( @@ -523,9 +549,7 @@ def serve( except KeyboardInterrupt: typer.Exit() except Exception as e: - error_message = f"❌ Failed to start Arcade Worker: {escape(str(e))}" - console.print(error_message, style="bold red") - typer.Exit(code=1) + handle_cli_error("Failed to start Arcade Worker", e, debug) @cli.command( @@ -574,9 +598,7 @@ def workerup( except KeyboardInterrupt: typer.Exit() except Exception as e: - error_message = f"❌ Failed to start Arcade Toolkit Server: {escape(str(e))}" - console.print(error_message, style="bold red") - typer.Exit(code=1) + handle_cli_error("Failed to start Arcade Toolkit Server", e, debug) @cli.command(help="Deploy toolkits to Arcade Cloud", rich_help_panel="Deployment") @@ -620,6 +642,7 @@ def deploy( "--no-tls", help="Whether to disable TLS for the connection to the Arcade Engine.", ), + debug: bool = typer.Option(False, "--debug", help="Show debug information"), ) -> None: """ Deploy a worker to Arcade Cloud. @@ -637,8 +660,7 @@ def deploy( try: deployment = Deployment.from_toml(Path(deployment_file)) except Exception as e: - console.print(f"❌ Failed to parse deployment file: {e}", style="bold red") - raise typer.Exit(code=1) + handle_cli_error("Failed to parse deployment file", e, debug) with console.status(f"Deploying {len(deployment.worker)} workers"): for worker in deployment.worker: @@ -648,10 +670,7 @@ def deploy( worker.request().execute(cloud_client, engine_client) console.log(f"✅ Worker '{worker.config.id}' deployed successfully.", style="dim") except Exception as e: - console.log( - f"❌ Failed to deploy worker '{worker.config.id}': {e}", style="bold red" - ) - raise typer.Exit(code=1) + handle_cli_error(f"Failed to deploy worker '{worker.config.id}'", e, debug) @cli.command(help="Open the Arcade Dashboard in a web browser", rich_help_panel="User") @@ -684,30 +703,34 @@ def dashboard( "--no-tls", help="Whether to disable TLS for the connection to the Arcade Engine.", ), + debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"), ) -> None: """Opens the Arcade Dashboard in a web browser. The Dashboard is a web-based Arcade user interface that is served by the Arcade Engine. """ - if local: - host = "localhost" + try: + if local: + host = "localhost" - # Construct base URL (for both health check and dashboard) - base_url = compute_base_url(force_tls, force_no_tls, host, port) - dashboard_url = f"{base_url}/dashboard" + # Construct base URL (for both health check and dashboard) + base_url = compute_base_url(force_tls, force_no_tls, host, port) + dashboard_url = f"{base_url}/dashboard" - # Try to hit /health endpoint on engine and warn if it is down - config = validate_and_get_config() - with Arcade(api_key=config.api.key, base_url=base_url) as client: - log_engine_health(client) + # Try to hit /health endpoint on engine and warn if it is down + config = validate_and_get_config() + with Arcade(api_key=config.api.key, base_url=base_url) as client: + log_engine_health(client) - # Open the dashboard in a browser - console.print(f"Opening Arcade Dashboard at {dashboard_url}") - if not webbrowser.open(dashboard_url): - console.print( - f"If a browser doesn't open automatically, copy this URL and paste it into your browser: {dashboard_url}", - style="dim", - ) + # Open the dashboard in a browser + console.print(f"Opening Arcade Dashboard at {dashboard_url}") + if not webbrowser.open(dashboard_url): + console.print( + f"If a browser doesn't open automatically, copy this URL and paste it into your browser: {dashboard_url}", + style="dim", + ) + except Exception as e: + handle_cli_error("Failed to open dashboard", e, debug) @cli.callback()