--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 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()