Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions libs/arcade-cli/arcade_cli/authn.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ def save_credentials_from_whoami(
tokens: TokenResponse,
whoami: WhoAmIResponse,
coordinator_url: str,
engine_url: str | None = None,
) -> None:
"""
Save OAuth credentials to the config file using WhoAmI response.
Expand All @@ -485,6 +486,10 @@ def save_credentials_from_whoami(
Args:
tokens: OAuth tokens
whoami: Response from /whoami endpoint with user and orgs/projects
coordinator_url: Coordinator URL that was used for the OAuth flow
engine_url: Matching Engine URL for the same environment, persisted so
subsequent CLI commands default to it. ``None`` leaves the field
unset, which causes commands to fall back to the prod default.
"""
# Ensure config directory exists
os.makedirs(ARCADE_CONFIG_PATH, exist_ok=True)
Expand All @@ -505,6 +510,7 @@ def save_credentials_from_whoami(

config = Config(
coordinator_url=coordinator_url,
engine_url=engine_url,
auth=AuthConfig(
access_token=tokens.access_token,
refresh_token=tokens.refresh_token,
Expand Down
6 changes: 3 additions & 3 deletions libs/arcade-cli/arcade_cli/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
from arcade_cli.console import console
from arcade_cli.secret import load_env_file
from arcade_cli.utils import (
compute_base_url,
get_auth_headers,
get_org_scoped_url,
resolve_engine_url,
validate_and_get_config,
)

Expand Down Expand Up @@ -814,7 +814,7 @@ def deploy_server_logic(
server_name: str | None,
server_version: str | None,
secrets: str,
host: str,
host: str | None,
port: int | None,
force_tls: bool,
force_no_tls: bool,
Expand All @@ -839,7 +839,7 @@ def deploy_server_logic(
# Step 1: Validate user is logged in
console.print("\nValidating user is logged in...", style="dim")
config = validate_and_get_config()
engine_url = compute_base_url(force_tls, force_no_tls, host, port)
engine_url = resolve_engine_url(host, port, force_tls, force_no_tls)
user_email = config.user.email if config.user else "User"
console.print(f"✓ {user_email} is logged in", style="green")

Expand Down
54 changes: 39 additions & 15 deletions libs/arcade-cli/arcade_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from arcade_cli.utils import (
ModelSpec,
Provider,
compute_base_url,
derive_engine_url_from_coordinator,
expand_provider_configs,
get_default_model,
get_eval_files,
Expand All @@ -44,6 +44,7 @@
parse_output_paths,
parse_provider_spec,
require_dependency,
resolve_engine_url,
resolve_provider_api_keys,
version_callback,
)
Expand Down Expand Up @@ -108,6 +109,7 @@ def login(
return

coordinator_url = build_coordinator_url(host, port)
engine_url = derive_engine_url_from_coordinator(coordinator_url)

try:
result = perform_oauth_login(
Expand All @@ -116,16 +118,17 @@ def login(
callback_timeout_seconds=timeout,
)

# Save credentials
save_credentials_from_whoami(result.tokens, result.whoami, coordinator_url)
save_credentials_from_whoami(result.tokens, result.whoami, coordinator_url, engine_url)

# Success message
console.print(f"\n✅ Logged in as {result.email}.", style="bold green")
if result.selected_org and result.selected_project:
console.print(
f"\nActive project: {result.selected_org.name} / {result.selected_project.name}",
style="dim",
)
console.print(f"Coordinator: {coordinator_url}", style="dim")
if engine_url:
console.print(f"Engine: {engine_url}", style="dim")
console.print(
"Run 'arcade org list' or 'arcade project list' to see available options.",
style="dim",
Expand Down Expand Up @@ -200,6 +203,19 @@ def whoami(
else:
console.print("\nNo active organization/project set.", style="yellow")

coordinator_display = config.coordinator_url or f"https://{PROD_COORDINATOR_HOST}"
engine_display = config.engine_url or f"https://{PROD_ENGINE_HOST} (fallback)"
coordinator_style = (
"bold yellow"
if config.coordinator_url and PROD_COORDINATOR_HOST not in config.coordinator_url
else "bold"
)
engine_style = (
"bold yellow" if config.engine_url and PROD_ENGINE_HOST not in config.engine_url else "bold"
)
console.print(f"\nCoordinator: {coordinator_display}", style=coordinator_style)
console.print(f"Engine: {engine_display}", style=engine_style)

console.print("\nRun 'arcade org list' or 'arcade project list' to see options.", style="dim")


Expand Down Expand Up @@ -363,11 +379,14 @@ def show(
tool: Optional[str] = typer.Option(
None, "-t", "--tool", help="The specific tool to show details for"
),
host: str = typer.Option(
PROD_ENGINE_HOST,
host: Optional[str] = typer.Option(
None,
"-h",
"--host",
help="The Arcade Engine address to show the tools/servers of.",
help=(
"The Arcade Engine address to show the tools/servers of. "
"Defaults to the host from `arcade login`, then `api.arcade.dev`."
),
),
local: bool = typer.Option(
False,
Expand Down Expand Up @@ -946,11 +965,14 @@ def deploy(
rich_help_panel="Advanced",
click_type=click.Choice(["auto", "all", "skip"], case_sensitive=False),
),
host: str = typer.Option(
PROD_ENGINE_HOST,
host: Optional[str] = typer.Option(
None,
"--host",
"-h",
help="The Arcade Engine host to deploy to",
help=(
"The Arcade Engine host to deploy to. "
"Defaults to the host from `arcade login`, then `api.arcade.dev`."
),
hidden=True,
),
port: Optional[int] = typer.Option(
Expand Down Expand Up @@ -1039,11 +1061,14 @@ def upgrade(

@cli.command(help="Open the Arcade Dashboard in a web browser", rich_help_panel="User")
def dashboard(
host: str = typer.Option(
PROD_ENGINE_HOST,
host: Optional[str] = typer.Option(
None,
"-h",
"--host",
help="The Arcade Engine host that serves the dashboard.",
help=(
"The Arcade Engine host that serves the dashboard. "
"Defaults to the host from `arcade login`, then `api.arcade.dev`."
),
),
port: Optional[int] = typer.Option(
None,
Expand Down Expand Up @@ -1077,8 +1102,7 @@ def dashboard(
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)
base_url = resolve_engine_url(host, port, force_tls, force_no_tls)
dashboard_url = f"{base_url}/dashboard"

# Try to hit /health endpoint on engine and warn if it is down
Expand Down
29 changes: 12 additions & 17 deletions libs/arcade-cli/arcade_cli/org.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Optional

import typer
from arcade_core.constants import PROD_COORDINATOR_HOST

from arcade_cli.authn import (
fetch_organizations,
Expand All @@ -9,8 +10,8 @@
from arcade_cli.console import console
from arcade_cli.usage.command_tracker import TrackedTyper, TrackedTyperGroup
from arcade_cli.utils import (
compute_base_url,
handle_cli_error,
resolve_coordinator_url,
)

app = TrackedTyper(
Expand All @@ -22,26 +23,21 @@
pretty_exceptions_short=True,
)

state = {
"coordinator_url": compute_base_url(
force_tls=False,
force_no_tls=False,
host=PROD_COORDINATOR_HOST,
port=None,
default_port=None,
)
}
state: dict[str, str] = {}


@app.callback()
def main(
host: str = typer.Option(
PROD_COORDINATOR_HOST,
host: Optional[str] = typer.Option(
None,
"--host",
"-h",
help="The Arcade Coordinator host.",
help=(
"The Arcade Coordinator host. Defaults to the host from `arcade login`, "
"then `cloud.arcade.dev`."
),
),
port: int = typer.Option(
port: Optional[int] = typer.Option(
None,
"--port",
"-p",
Expand All @@ -59,8 +55,7 @@ def main(
),
) -> None:
"""Configure Coordinator connection options for organization commands."""
coordinator_url = compute_base_url(force_tls, force_no_tls, host, port, default_port=None)
state["coordinator_url"] = coordinator_url
state["coordinator_url"] = resolve_coordinator_url(host, port, force_tls, force_no_tls)


@app.command("list", help="List organizations you belong to")
Expand Down
29 changes: 12 additions & 17 deletions libs/arcade-cli/arcade_cli/project.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from typing import Optional

import typer
from arcade_core.constants import PROD_COORDINATOR_HOST

from arcade_cli.authn import fetch_projects
from arcade_cli.console import console
from arcade_cli.usage.command_tracker import TrackedTyper, TrackedTyperGroup
from arcade_cli.utils import (
compute_base_url,
handle_cli_error,
resolve_coordinator_url,
)

app = TrackedTyper(
Expand All @@ -18,26 +19,21 @@
pretty_exceptions_short=True,
)

state = {
"coordinator_url": compute_base_url(
force_tls=False,
force_no_tls=False,
host=PROD_COORDINATOR_HOST,
port=None,
default_port=None,
)
}
state: dict[str, str] = {}


@app.callback()
def main(
host: str = typer.Option(
PROD_COORDINATOR_HOST,
host: Optional[str] = typer.Option(
None,
"--host",
"-h",
help="The Arcade Coordinator host.",
help=(
"The Arcade Coordinator host. Defaults to the host from `arcade login`, "
"then `cloud.arcade.dev`."
),
),
port: int = typer.Option(
port: Optional[int] = typer.Option(
None,
"--port",
"-p",
Expand All @@ -55,8 +51,7 @@ def main(
),
) -> None:
"""Configure Coordinator connection options for project commands."""
coordinator_url = compute_base_url(force_tls, force_no_tls, host, port, default_port=None)
state["coordinator_url"] = coordinator_url
state["coordinator_url"] = resolve_coordinator_url(host, port, force_tls, force_no_tls)


@app.command("list", help="List projects in the active organization")
Expand Down
25 changes: 12 additions & 13 deletions libs/arcade-cli/arcade_cli/secret.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from typing import Optional

import httpx
import typer
from arcade_core.constants import PROD_ENGINE_HOST
from rich.table import Table

from arcade_cli.console import console
from arcade_cli.usage.command_tracker import TrackedTyper, TrackedTyperGroup
from arcade_cli.utils import (
compute_base_url,
get_auth_headers,
get_org_scoped_url,
resolve_engine_url,
)

app = TrackedTyper(
Expand All @@ -20,22 +21,21 @@
pretty_exceptions_short=True,
)

state = {
"engine_url": compute_base_url(
host=PROD_ENGINE_HOST, port=None, force_tls=False, force_no_tls=False
)
}
state: dict[str, str] = {}


@app.callback()
def main(
host: str = typer.Option(
PROD_ENGINE_HOST,
host: Optional[str] = typer.Option(
None,
"--host",
"-h",
help="The Arcade Engine host.",
help=(
"The Arcade Engine host. Defaults to the host from `arcade login`, "
"then `api.arcade.dev`."
),
),
port: int = typer.Option(
port: Optional[int] = typer.Option(
None,
"--port",
"-p",
Expand All @@ -62,8 +62,7 @@ def main(
arcade secret list
arcade secret unset KEY1 KEY2 KEY3
"""
engine_url = compute_base_url(force_tls, force_no_tls, host, port)
state["engine_url"] = engine_url
state["engine_url"] = resolve_engine_url(host, port, force_tls, force_no_tls)


@app.command("set", help="Set tool secret(s) using KEY=VALUE pairs or from .env file")
Expand Down
Loading
Loading