From 1b9f41d24432f2f068df9560f2c9be4ba41a54a1 Mon Sep 17 00:00:00 2001 From: bearsyankees Date: Thu, 27 Aug 2026 17:30:16 -0400 Subject: [PATCH] fix(cloud): preserve scopes when switching workspaces --- strix/interface/cloud/__init__.py | 6 ++++-- strix/interface/cloud/workspaces.py | 13 +++++++++++-- tests/test_cloud_cli.py | 23 ++++++++++++++++++++++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/strix/interface/cloud/__init__.py b/strix/interface/cloud/__init__.py index c93d0c09..f906a121 100644 --- a/strix/interface/cloud/__init__.py +++ b/strix/interface/cloud/__init__.py @@ -27,7 +27,8 @@ _USAGE_HEADER = """[bold]Usage:[/] strix cloud [arguments] [bold]Resource commands:[/]""" _USAGE_FOOTER = """ -Run [bold]strix cloud [/] without a verb to list its verbs. +Run [bold]strix cloud help[/] to list its verbs. Common read-only +commands may also run their default verb when no verb is given. Every command accepts [bold]--json[/] and [bold]--token[/]. Write commands accept [bold]--data[/] with a JSON object of extra request fields. API reference: https://docs.app.strix.ai""" @@ -57,7 +58,8 @@ def run_cloud(argv: list[str]) -> int: console.print(f"[red]Unknown command:[/] {group}") _print_usage(console) return 2 - resolved = resolve(group, rest) + group_help = bool(rest and rest[0] in ("-h", "--help", "help")) + resolved = None if group_help else resolve(group, rest) if resolved is None: _print_verbs(console, group) return 0 if not rest or rest[0] in ("-h", "--help", "help") else 2 diff --git a/strix/interface/cloud/workspaces.py b/strix/interface/cloud/workspaces.py index 4958cd73..0ff23fe5 100644 --- a/strix/interface/cloud/workspaces.py +++ b/strix/interface/cloud/workspaces.py @@ -31,7 +31,10 @@ def run_workspace_use(argv: list[str]) -> int: nargs="+", metavar="SCOPE", default=None, - help="API scopes for the new token. Without this option the server grants the defaults.", + help=( + "API scopes for the new token. Without this option, preserve the stored token's " + "scopes." + ), ) parser.add_argument("--json", action="store_true", help="Print the raw JSON response.") parser.add_argument("--token", default=None, help="API token override.") @@ -58,9 +61,16 @@ def run_workspace_use(argv: list[str]) -> int: def _use(console: Console, args: argparse.Namespace, *, as_json: bool) -> int: workspace = _find_workspace(args.workspace, token=args.token) + record = read_record() or {} body: dict[str, Any] = {} if args.scopes: body["scopes"] = args.scopes + elif args.token is None: + stored_scopes = record.get("scopes") + if isinstance(stored_scopes, list) and stored_scopes and all( + isinstance(scope, str) for scope in stored_scopes + ): + body["scopes"] = stored_scopes minted = http.check( http.request( "POST", @@ -73,7 +83,6 @@ def _use(console: Console, args: argparse.Namespace, *, as_json: bool) -> int: raise http.CloudError("the platform did not return a token.") minted_record = cast("dict[str, Any]", minted) - record = read_record() or {} record.update( { "api_token": minted_record["api_token"], diff --git a/tests/test_cloud_cli.py b/tests/test_cloud_cli.py index 03fbecf5..09dcb743 100644 --- a/tests/test_cloud_cli.py +++ b/tests/test_cloud_cli.py @@ -571,17 +571,26 @@ def test_workspaces_use_switches_stored_token( auth_path = tmp_path / "platform-auth.json" monkeypatch.setattr(platform_cli, "AUTH_PATH", auth_path) monkeypatch.setattr(workspaces, "AUTH_PATH", auth_path) - platform_cli.save_record({"api_token": "old", "email": "a@b.test"}) + platform_cli.save_record( + { + "api_token": "old", + "email": "a@b.test", + "scopes": ["scans:read", "organizations:read", "tokens:write"], + } + ) calls: list[tuple[str, str]] = [] + token_body: dict[str, Any] | None = None def fake_request(method: str, path: str, **kwargs: Any) -> FakeResponse: + nonlocal token_body calls.append((method, path)) if path == "/workspaces": return FakeResponse( status_code=200, payload={"workspaces": [{"id": "org_1", "name": "Team One", "role": "admin"}]}, ) + token_body = kwargs.get("body") return FakeResponse( status_code=201, payload={ @@ -596,6 +605,9 @@ def test_workspaces_use_switches_stored_token( code = cloud.run_cloud(["workspaces", "use", "team one", "--json"]) assert code == 0 assert calls == [("GET", "/workspaces"), ("POST", "/workspaces/org_1/token")] + assert token_body == { + "scopes": ["scans:read", "organizations:read", "tokens:write"] + } record = platform_cli.read_record() assert record is not None assert record["api_token"] == "new-token" @@ -613,3 +625,12 @@ def test_workspaces_use_reports_unknown_workspace(monkeypatch: pytest.MonkeyPatc ), ) assert cloud.run_cloud(["workspaces", "use", "missing", "--json"]) == 1 + + +def test_group_help_lists_all_verbs_instead_of_default_verb_help(capsys: Any) -> None: + assert cloud.run_cloud(["workspaces", "-h"]) == 0 + output = capsys.readouterr().out + assert "workspaces verbs" in output + assert "list" in output + assert "create" in output + assert "use" in output