--debug for CLI commands (#454)

```
(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
```
This commit is contained in:
Evan Tahler 2025-07-10 14:52:25 -07:00 committed by GitHub
parent 836c890840
commit 6889d8673b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
import os import os
import threading import threading
import traceback
import uuid import uuid
import webbrowser import webbrowser
from pathlib import Path from pathlib import Path
@ -69,6 +70,21 @@ cli.add_typer(
console = Console() 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") @cli.command(help="Log in to Arcade Cloud", rich_help_panel="User")
def login( def login(
host: str = typer.Option( host: str = typer.Option(
@ -83,6 +99,7 @@ def login(
"--port", "--port",
help="The port of the Arcade Cloud host (if running locally).", help="The port of the Arcade Cloud host (if running locally).",
), ),
debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"),
) -> None: ) -> None:
""" """
Logs the user into Arcade Cloud. Logs the user into Arcade Cloud.
@ -115,22 +132,29 @@ def login(
server_thread.join() server_thread.join()
except KeyboardInterrupt: except KeyboardInterrupt:
auth_server.shutdown_server() auth_server.shutdown_server()
except Exception as e:
handle_cli_error("Login failed", e, debug)
finally: finally:
if server_thread.is_alive(): if server_thread.is_alive():
server_thread.join() # Ensure the server thread completes and cleans up server_thread.join() # Ensure the server thread completes and cleans up
@cli.command(help="Log out of Arcade Cloud", rich_help_panel="User") @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. Logs the user out of Arcade Cloud.
""" """
# If the credentials file exists, delete it try:
if os.path.exists(CREDENTIALS_FILE_PATH): # If the credentials file exists, delete it
os.remove(CREDENTIALS_FILE_PATH) if os.path.exists(CREDENTIALS_FILE_PATH):
console.print("You're now logged out.", style="bold") os.remove(CREDENTIALS_FILE_PATH)
else: console.print("You're now logged out.", style="bold")
console.print("You're not logged in.", style="bold red") else:
console.print("You're not logged in.", style="bold red")
except Exception as e:
handle_cli_error("Logout failed", e, debug)
@cli.command( @cli.command(
@ -143,6 +167,7 @@ def new(
metavar="TOOLKIT_NAME", metavar="TOOLKIT_NAME",
), ),
directory: str = typer.Option(os.getcwd(), "--dir", help="tools directory path"), directory: str = typer.Option(os.getcwd(), "--dir", help="tools directory path"),
debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"),
) -> None: ) -> None:
""" """
Creates a new toolkit with the given name, description, and result type. Creates a new toolkit with the given name, description, and result type.
@ -152,8 +177,7 @@ def new(
try: try:
create_new_toolkit(directory, toolkit_name) create_new_toolkit(directory, toolkit_name)
except Exception as e: except Exception as e:
error_message = f"❌ Failed to create new Toolkit: {escape(str(e))}" handle_cli_error("Failed to create new Toolkit", e, debug)
console.print(error_message, style="bold red")
@cli.command( @cli.command(
@ -318,7 +342,7 @@ def chat(
tool_messages = chat_result.tool_messages tool_messages = chat_result.tool_messages
except OpenAIError as e: 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 continue
if debug: if debug:
display_tool_messages(tool_messages) display_tool_messages(tool_messages)
@ -328,9 +352,7 @@ def chat(
typer.Exit() typer.Exit()
except RuntimeError as e: except RuntimeError as e:
error_message = f"❌ Failed to run tool{': ' + escape(str(e)) if str(e) else ''}" handle_cli_error("Failed to run tool", e, debug)
console.print(error_message, style="bold red")
raise typer.Exit()
@cli.command(help="Run tool calling evaluations", rich_help_panel="Tool Development") @cli.command(help="Run tool calling evaluations", rich_help_panel="Tool Development")
@ -376,6 +398,7 @@ def evals(
"--no-tls", "--no-tls",
help="Whether to disable TLS for the connection to the Arcade Engine.", help="Whether to disable TLS for the connection to the Arcade Engine.",
), ),
debug: bool = typer.Option(False, "--debug", help="Show debug information"),
) -> None: ) -> None:
""" """
Find all files starting with 'eval_' in the given directory, Find all files starting with 'eval_' in the given directory,
@ -463,7 +486,10 @@ def evals(
all_evaluations.extend(results) all_evaluations.extend(results)
display_eval_results(all_evaluations, show_details=show_details) 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( @cli.command(
@ -523,9 +549,7 @@ def serve(
except KeyboardInterrupt: except KeyboardInterrupt:
typer.Exit() typer.Exit()
except Exception as e: except Exception as e:
error_message = f"❌ Failed to start Arcade Worker: {escape(str(e))}" handle_cli_error("Failed to start Arcade Worker", e, debug)
console.print(error_message, style="bold red")
typer.Exit(code=1)
@cli.command( @cli.command(
@ -574,9 +598,7 @@ def workerup(
except KeyboardInterrupt: except KeyboardInterrupt:
typer.Exit() typer.Exit()
except Exception as e: except Exception as e:
error_message = f"❌ Failed to start Arcade Toolkit Server: {escape(str(e))}" handle_cli_error("Failed to start Arcade Toolkit Server", e, debug)
console.print(error_message, style="bold red")
typer.Exit(code=1)
@cli.command(help="Deploy toolkits to Arcade Cloud", rich_help_panel="Deployment") @cli.command(help="Deploy toolkits to Arcade Cloud", rich_help_panel="Deployment")
@ -620,6 +642,7 @@ def deploy(
"--no-tls", "--no-tls",
help="Whether to disable TLS for the connection to the Arcade Engine.", help="Whether to disable TLS for the connection to the Arcade Engine.",
), ),
debug: bool = typer.Option(False, "--debug", help="Show debug information"),
) -> None: ) -> None:
""" """
Deploy a worker to Arcade Cloud. Deploy a worker to Arcade Cloud.
@ -637,8 +660,7 @@ def deploy(
try: try:
deployment = Deployment.from_toml(Path(deployment_file)) deployment = Deployment.from_toml(Path(deployment_file))
except Exception as e: except Exception as e:
console.print(f"❌ Failed to parse deployment file: {e}", style="bold red") handle_cli_error("Failed to parse deployment file", e, debug)
raise typer.Exit(code=1)
with console.status(f"Deploying {len(deployment.worker)} workers"): with console.status(f"Deploying {len(deployment.worker)} workers"):
for worker in deployment.worker: for worker in deployment.worker:
@ -648,10 +670,7 @@ def deploy(
worker.request().execute(cloud_client, engine_client) worker.request().execute(cloud_client, engine_client)
console.log(f"✅ Worker '{worker.config.id}' deployed successfully.", style="dim") console.log(f"✅ Worker '{worker.config.id}' deployed successfully.", style="dim")
except Exception as e: except Exception as e:
console.log( handle_cli_error(f"Failed to deploy worker '{worker.config.id}'", e, debug)
f"❌ Failed to deploy worker '{worker.config.id}': {e}", style="bold red"
)
raise typer.Exit(code=1)
@cli.command(help="Open the Arcade Dashboard in a web browser", rich_help_panel="User") @cli.command(help="Open the Arcade Dashboard in a web browser", rich_help_panel="User")
@ -684,30 +703,34 @@ def dashboard(
"--no-tls", "--no-tls",
help="Whether to disable TLS for the connection to the Arcade Engine.", help="Whether to disable TLS for the connection to the Arcade Engine.",
), ),
debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"),
) -> None: ) -> None:
"""Opens the Arcade Dashboard in a web browser. """Opens the Arcade Dashboard in a web browser.
The Dashboard is a web-based Arcade user interface that is served by the Arcade Engine. The Dashboard is a web-based Arcade user interface that is served by the Arcade Engine.
""" """
if local: try:
host = "localhost" if local:
host = "localhost"
# Construct base URL (for both health check and dashboard) # Construct base URL (for both health check and dashboard)
base_url = compute_base_url(force_tls, force_no_tls, host, port) base_url = compute_base_url(force_tls, force_no_tls, host, port)
dashboard_url = f"{base_url}/dashboard" dashboard_url = f"{base_url}/dashboard"
# Try to hit /health endpoint on engine and warn if it is down # Try to hit /health endpoint on engine and warn if it is down
config = validate_and_get_config() config = validate_and_get_config()
with Arcade(api_key=config.api.key, base_url=base_url) as client: with Arcade(api_key=config.api.key, base_url=base_url) as client:
log_engine_health(client) log_engine_health(client)
# Open the dashboard in a browser # Open the dashboard in a browser
console.print(f"Opening Arcade Dashboard at {dashboard_url}") console.print(f"Opening Arcade Dashboard at {dashboard_url}")
if not webbrowser.open(dashboard_url): if not webbrowser.open(dashboard_url):
console.print( console.print(
f"If a browser doesn't open automatically, copy this URL and paste it into your browser: {dashboard_url}", f"If a browser doesn't open automatically, copy this URL and paste it into your browser: {dashboard_url}",
style="dim", style="dim",
) )
except Exception as e:
handle_cli_error("Failed to open dashboard", e, debug)
@cli.callback() @cli.callback()