Files
trycua--cua/libs/python/cua-cli/cua_cli/commands/workspace.py
T
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

206 lines
6.7 KiB
Python

"""Workspace commands for Cua CLI."""
import argparse
import asyncio
import os
import aiohttp
from cua_cli.auth.browser import authenticate_via_browser
from cua_cli.auth.store import (
_get_store,
delete_workspace,
get_workspace_api_key,
list_workspaces,
save_workspace,
set_active_workspace,
)
from cua_cli.utils.async_utils import run_async
from cua_cli.utils.output import print_error, print_info, print_success
from cua_core.http import cua_version_headers
DEFAULT_API_BASE = "https://api.cua.ai"
def _get_api_base() -> str:
return os.environ.get("CUA_API_BASE", DEFAULT_API_BASE).rstrip("/")
def register_parser(subparsers: argparse._SubParsersAction) -> None:
"""Register the workspace command and subcommands."""
ws_parser = subparsers.add_parser(
"workspace",
help="Workspace commands",
description="Manage workspaces",
aliases=["ws"],
)
ws_subparsers = ws_parser.add_subparsers(
dest="workspace_command",
help="Workspace command",
)
set_parser = ws_subparsers.add_parser(
"set",
help="Switch active workspace",
description="Switch to a different workspace",
)
set_parser.add_argument(
"slug",
nargs="?",
help="Workspace slug to switch to",
)
def execute(args: argparse.Namespace) -> int:
"""Execute workspace command based on subcommand."""
cmd = getattr(args, "workspace_command", None)
if cmd == "set":
return cmd_set(args)
else:
print_error("Usage: cua workspace <command>")
print_info("Commands: set")
return 1
def _validate_workspace_key(api_key: str) -> tuple[bool | None, dict]:
"""Validate an API key by calling /v1/me.
Returns:
(True, data) — valid key (HTTP 200)
(False, data) — explicitly invalid/expired (HTTP 401)
(None, {}) — transport error or unexpected HTTP status
"""
async def _do():
url = f"{_get_api_base()}/v1/me"
headers = {
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
**cua_version_headers(),
}
async with aiohttp.ClientSession() as session:
timeout = aiohttp.ClientTimeout(total=10)
async with session.get(url, headers=headers, timeout=timeout) as resp:
try:
data = await resp.json(content_type=None)
except Exception:
return resp.status, {}
return resp.status, data
try:
status_code, data = run_async(_do())
if status_code == 200:
return True, data
elif status_code == 401:
return False, data
else:
return None, {}
except (aiohttp.ClientError, asyncio.TimeoutError, OSError):
return None, {}
except Exception:
return None, {}
def cmd_set(args: argparse.Namespace) -> int:
"""Handle the workspace set command."""
slug = getattr(args, "slug", None)
if slug:
# Check if we have a stored key for this workspace
stored_key = get_workspace_api_key(slug)
if stored_key:
# Validate the stored key
valid, _data = _validate_workspace_key(stored_key)
if valid is True:
set_active_workspace(slug)
ws_name = _get_store().get(f"workspace:{slug}:name") or slug
print_success(f"Switched to workspace: {ws_name} ({slug})")
return 0
elif valid is None:
# Transport error — don't delete cached credentials
print_error(
f"Could not verify credentials for '{slug}' (API unreachable). Try again later."
)
return 1
else:
# Explicitly expired (401) — remove and fall through to browser auth
print_info(f"Credentials for '{slug}' are expired. Re-authenticating...")
delete_workspace(slug)
# Not authenticated or stale — trigger browser auth with hint
try:
result = run_async(authenticate_via_browser(workspace_slug=slug))
except (TimeoutError, RuntimeError) as e:
print_error(str(e))
return 1
if result.workspace_slug:
if result.workspace_slug != slug:
print_error(
f"Authenticated workspace '{result.workspace_slug}' does not match "
f"requested workspace '{slug}'."
)
return 1
save_workspace(
result.workspace_slug,
result.token,
result.workspace_name,
result.org_name,
)
set_active_workspace(result.workspace_slug)
print_success(
f"Authenticated and switched to workspace: "
f"{result.workspace_name} ({result.workspace_slug})"
)
else:
print_error("Authentication did not return workspace metadata.")
return 1
return 0
# No slug — interactive selection from authenticated workspaces
workspaces = list_workspaces()
if not workspaces:
print_info("No authenticated workspaces. Run 'cua auth login' to add one.")
return 0
print_info("Select a workspace:")
for i, ws in enumerate(workspaces, 1):
marker = " (active)" if ws["is_active"] else ""
name_part = f" - {ws['name']}" if ws["name"] else ""
print(f" {i}. {ws['slug']}{name_part}{marker}")
try:
choice = input("\nEnter number: ").strip()
idx = int(choice) - 1
if idx < 0 or idx >= len(workspaces):
print_error("Invalid selection.")
return 1
except (ValueError, EOFError, KeyboardInterrupt):
print_error("Invalid selection.")
return 1
selected = workspaces[idx]
# Validate the selected workspace's session before switching
stored_key = get_workspace_api_key(selected["slug"])
if stored_key:
valid, _data = _validate_workspace_key(stored_key)
if valid is None:
print_error(
f"Could not verify credentials for '{selected['slug']}' (API unreachable). "
f"Try again later."
)
return 1
elif valid is False:
delete_workspace(selected["slug"])
print_error(
f"Credentials for '{selected['slug']}' are expired. "
f"Run 'cua auth login' to re-authenticate."
)
return 1
set_active_workspace(selected["slug"])
print_success(f"Switched to workspace: {selected['name']} ({selected['slug']})")
return 0