softmax-cli 0.26.8__tar.gz → 0.26.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {softmax_cli-0.26.8/src/softmax_cli.egg-info → softmax_cli-0.26.9}/PKG-INFO +1 -1
  2. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax/auth.py +5 -0
  3. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax/cli.py +62 -43
  4. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax/cogames.py +6 -9
  5. {softmax_cli-0.26.8 → softmax_cli-0.26.9/src/softmax_cli.egg-info}/PKG-INFO +1 -1
  6. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/tests/test_auth_login.py +2 -6
  7. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/tests/test_python_api.py +3 -7
  8. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/BUILD.bazel +0 -0
  9. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/pyproject.toml +0 -0
  10. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/setup.cfg +0 -0
  11. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax/__init__.py +0 -0
  12. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax/_console.py +0 -0
  13. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax/perform_login.py +0 -0
  14. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax/token_storage.py +0 -0
  15. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax_cli.egg-info/SOURCES.txt +0 -0
  16. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax_cli.egg-info/dependency_links.txt +0 -0
  17. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax_cli.egg-info/entry_points.txt +0 -0
  18. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax_cli.egg-info/requires.txt +0 -0
  19. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/src/softmax_cli.egg-info/top_level.txt +0 -0
  20. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/tests/BUILD.bazel +0 -0
  21. {softmax_cli-0.26.8 → softmax_cli-0.26.9}/tests/test_cli_plugins.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: softmax-cli
3
- Version: 0.26.8
3
+ Version: 0.26.9
4
4
  Summary: Softmax CLI — authentication and account tools
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
5
6
  from urllib.parse import urlencode, urlsplit, urlunsplit
6
7
 
7
8
  import httpx
@@ -16,6 +17,10 @@ DEFAULT_COGAMES_SERVER = "https://softmax.com/api"
16
17
  DEFAULT_COGAMES_API_SERVER = "https://api.observatory.softmax-research.net"
17
18
 
18
19
 
20
+ def get_login_server() -> str:
21
+ return os.environ.get("COGAMES_LOGIN_URL", DEFAULT_COGAMES_SERVER)
22
+
23
+
19
24
  class WhoAmIResponse(BaseModel):
20
25
  user_email: str
21
26
  is_softmax_team_member: bool = False
@@ -4,6 +4,7 @@ import importlib
4
4
  import importlib.util
5
5
  import sys
6
6
 
7
+ import httpx
7
8
  import typer
8
9
  from rich.panel import Panel
9
10
 
@@ -13,6 +14,7 @@ from softmax.auth import (
13
14
  build_browser_login_url,
14
15
  delete_cogames_tokens,
15
16
  fetch_cogames_whoami,
17
+ get_login_server,
16
18
  load_cogames_user_token,
17
19
  load_current_cogames_token,
18
20
  save_cogames_active_token,
@@ -39,15 +41,16 @@ def _register_optional_apps() -> None:
39
41
  app.add_typer(cogames_cli.app, name="cogames", rich_help_panel="Local Games")
40
42
 
41
43
 
42
- def _build_manual_set_token_command(*, login_server: str) -> str:
43
- command = "softmax set-token '<TOKEN>'"
44
- if login_server != DEFAULT_COGAMES_SERVER:
45
- command += f" --login-server '{login_server}'"
46
- return command
44
+ def _build_manual_set_token_command(server: str | None = None) -> str:
45
+ if server:
46
+ return f"softmax set-token --server '{server}' '<TOKEN>'"
47
+ return "softmax set-token '<TOKEN>'"
47
48
 
48
49
 
49
- def _print_non_tty_login_instructions(*, login_server: str) -> None:
50
- auth_url = build_browser_login_url(login_server)
50
+ def _print_non_tty_login_instructions(login_server: str | None = None) -> None:
51
+ resolved = login_server or get_login_server()
52
+ is_custom = resolved != DEFAULT_COGAMES_SERVER
53
+ auth_url = build_browser_login_url(resolved)
51
54
  console.print("Interactive login requires a TTY.", style="red")
52
55
  console.print()
53
56
  console.print("Open this URL in any browser to sign in:", style="yellow")
@@ -56,7 +59,7 @@ def _print_non_tty_login_instructions(*, login_server: str) -> None:
56
59
  console.print()
57
60
  console.print("Copy the auth token from the browser, then run:", style="yellow")
58
61
  console.print()
59
- console.print(" ", _build_manual_set_token_command(login_server=login_server))
62
+ console.print(" ", _build_manual_set_token_command(resolved if is_custom else None))
60
63
  console.print()
61
64
  console.print(
62
65
  Panel(
@@ -70,12 +73,6 @@ def _print_non_tty_login_instructions(*, login_server: str) -> None:
70
73
 
71
74
  @app.command(name="login")
72
75
  def login_cmd(
73
- login_server: str = typer.Option(
74
- DEFAULT_COGAMES_SERVER,
75
- "--login-server",
76
- metavar="URL",
77
- help="Authentication server URL",
78
- ),
79
76
  no_browser: bool = typer.Option(
80
77
  False,
81
78
  "--no-browser",
@@ -87,18 +84,40 @@ def login_cmd(
87
84
  "-f",
88
85
  help="Re-authenticate even if already logged in",
89
86
  ),
87
+ server: str | None = typer.Option(
88
+ None,
89
+ "--server",
90
+ "-s",
91
+ metavar="URL",
92
+ help="Authentication server URL.",
93
+ ),
90
94
  ) -> None:
91
95
  """Sign in to Softmax."""
92
96
  from urllib.parse import urlparse # noqa: PLC0415
93
97
 
98
+ login_server = server or get_login_server()
99
+ is_default_server = login_server == DEFAULT_COGAMES_SERVER
94
100
  user_token = None if force else load_cogames_user_token(login_server=login_server)
101
+ if user_token is not None:
102
+ api_server = None if is_default_server else login_server
103
+ try:
104
+ fetch_cogames_whoami(api_server=api_server, token=user_token)
105
+ except httpx.HTTPStatusError as exc:
106
+ if exc.response.status_code == 401:
107
+ console.print("Saved token is no longer valid. Re-authenticating...", style="yellow")
108
+ user_token = None
109
+ else:
110
+ console.print(f"Could not verify token (HTTP {exc.response.status_code}), proceeding.", style="yellow")
111
+ except httpx.HTTPError:
112
+ console.print("Could not reach server to verify token. Proceeding with saved token.", style="yellow")
113
+
95
114
  if user_token is not None:
96
115
  save_cogames_active_token(login_server=login_server, token=user_token)
97
116
  console.print(f"Already authenticated with {urlparse(login_server).hostname}", style="green")
98
117
  return
99
118
 
100
119
  if not sys.stdin.isatty():
101
- _print_non_tty_login_instructions(login_server=login_server)
120
+ _print_non_tty_login_instructions(login_server)
102
121
  raise typer.Exit(1)
103
122
 
104
123
  try:
@@ -110,7 +129,7 @@ def login_cmd(
110
129
  "If you are a coding agent, ask your human to open the URL below and give you "
111
130
  "the auth token. Then paste the token into this window or run:\n"
112
131
  "\n"
113
- f"{_build_manual_set_token_command(login_server=login_server)}"
132
+ f"{_build_manual_set_token_command(server)}"
114
133
  ),
115
134
  open_browser=not no_browser,
116
135
  )
@@ -128,14 +147,16 @@ def login_cmd(
128
147
 
129
148
  @app.command(name="logout")
130
149
  def logout_cmd(
131
- login_server: str = typer.Option(
132
- DEFAULT_COGAMES_SERVER,
133
- "--login-server",
150
+ server: str | None = typer.Option(
151
+ None,
152
+ "--server",
153
+ "-s",
134
154
  metavar="URL",
135
- help="Authentication server URL",
155
+ help="Authentication server URL.",
136
156
  ),
137
157
  ) -> None:
138
158
  """Remove saved authentication token."""
159
+ login_server = server or get_login_server()
139
160
  if delete_cogames_tokens(login_server=login_server):
140
161
  console.print("Logged out.", style="green")
141
162
  else:
@@ -144,42 +165,36 @@ def logout_cmd(
144
165
 
145
166
  @app.command(name="get-login-url")
146
167
  def get_login_url_cmd(
147
- login_server: str = typer.Option(
148
- DEFAULT_COGAMES_SERVER,
149
- "--login-server",
168
+ server: str | None = typer.Option(
169
+ None,
170
+ "--server",
171
+ "-s",
150
172
  metavar="URL",
151
- help="Authentication server URL",
173
+ help="Authentication server URL.",
152
174
  ),
153
175
  ) -> None:
154
176
  """Print a browser sign-in URL for manual login."""
155
- print(build_browser_login_url(login_server))
177
+ print(build_browser_login_url(server or get_login_server()))
156
178
 
157
179
 
158
180
  @app.command(name="status")
159
181
  def status_cmd(
160
- login_server: str = typer.Option(
161
- DEFAULT_COGAMES_SERVER,
162
- "--login-server",
163
- metavar="URL",
164
- help="Authentication server URL",
165
- ),
166
182
  server: str | None = typer.Option(
167
183
  None,
168
184
  "--server",
169
185
  "-s",
170
186
  metavar="URL",
171
- help="API server URL for /whoami verification. Defaults to --login-server when"
172
- " that is overridden, otherwise the production Observatory API.",
187
+ help="Authentication server URL.",
173
188
  ),
174
189
  ) -> None:
175
190
  """Check authentication status via /whoami."""
191
+ login_server = server or get_login_server()
176
192
  token = load_current_cogames_token(login_server=login_server)
177
193
  if not token:
178
194
  console.print("[red]Not authenticated.[/red] Run [cyan]softmax login[/cyan] first.")
179
195
  raise typer.Exit(1)
180
196
 
181
- api_server = server or (login_server if login_server != DEFAULT_COGAMES_SERVER else None)
182
- session = fetch_cogames_whoami(api_server=api_server, token=token)
197
+ session = fetch_cogames_whoami(api_server=server, token=token)
183
198
  console.print("[green]Authenticated[/green]")
184
199
  console.print(f"user_email: {session.user_email}")
185
200
  console.print(f"subject_type: {session.subject_type}")
@@ -189,14 +204,16 @@ def status_cmd(
189
204
 
190
205
  @app.command(name="get-token")
191
206
  def get_token_cmd(
192
- login_server: str = typer.Option(
193
- DEFAULT_COGAMES_SERVER,
194
- "--login-server",
207
+ server: str | None = typer.Option(
208
+ None,
209
+ "--server",
210
+ "-s",
195
211
  metavar="URL",
196
- help="Authentication server URL",
212
+ help="Authentication server URL.",
197
213
  ),
198
214
  ) -> None:
199
215
  """Print the saved token to stdout (for scripting)."""
216
+ login_server = server or get_login_server()
200
217
  token = load_current_cogames_token(login_server=login_server)
201
218
  if not token:
202
219
  console.print("[red]No token found.[/red] Run [cyan]softmax login[/cyan] first.", style="bold")
@@ -207,14 +224,16 @@ def get_token_cmd(
207
224
  @app.command(name="set-token")
208
225
  def set_token_cmd(
209
226
  token: str = typer.Argument(help="Bearer token to save"),
210
- login_server: str = typer.Option(
211
- DEFAULT_COGAMES_SERVER,
212
- "--login-server",
227
+ server: str | None = typer.Option(
228
+ None,
229
+ "--server",
230
+ "-s",
213
231
  metavar="URL",
214
- help="Authentication server URL",
232
+ help="Authentication server URL.",
215
233
  ),
216
234
  ) -> None:
217
235
  """Manually set a token (for CI or headless environments)."""
236
+ login_server = server or get_login_server()
218
237
  save_cogames_user_token(login_server=login_server, token=token)
219
238
  print(f"\nToken saved for {login_server}")
220
239
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import importlib
4
4
  from typing import Any
5
5
 
6
- from softmax.auth import DEFAULT_COGAMES_API_SERVER, DEFAULT_COGAMES_SERVER
6
+ from softmax.auth import DEFAULT_COGAMES_API_SERVER
7
7
 
8
8
 
9
9
  def _get_tournament_client_class() -> type[Any]:
@@ -16,9 +16,9 @@ def _get_tournament_client_class() -> type[Any]:
16
16
  return tournament_client_module.TournamentServerClient
17
17
 
18
18
 
19
- def _create_client(*, token: str, server: str, login_server: str) -> Any:
19
+ def _create_client(*, token: str, server: str) -> Any:
20
20
  client_class = _get_tournament_client_class()
21
- return client_class(server_url=server, token=token, login_server=login_server)
21
+ return client_class(server_url=server, token=token)
22
22
 
23
23
 
24
24
  class _PlayerAPI:
@@ -27,9 +27,8 @@ class _PlayerAPI:
27
27
  token: str,
28
28
  *,
29
29
  server: str = DEFAULT_COGAMES_API_SERVER,
30
- login_server: str = DEFAULT_COGAMES_SERVER,
31
30
  ) -> list[Any]:
32
- with _create_client(token=token, server=server, login_server=login_server) as client:
31
+ with _create_client(token=token, server=server) as client:
33
32
  return client.list_players()
34
33
 
35
34
 
@@ -41,9 +40,8 @@ def login(
41
40
  player_id: str,
42
41
  *,
43
42
  server: str = DEFAULT_COGAMES_API_SERVER,
44
- login_server: str = DEFAULT_COGAMES_SERVER,
45
43
  ) -> str:
46
- with _create_client(token=token, server=server, login_server=login_server) as client:
44
+ with _create_client(token=token, server=server) as client:
47
45
  return client.login_player(player_id).token
48
46
 
49
47
 
@@ -52,9 +50,8 @@ def login_response(
52
50
  player_id: str,
53
51
  *,
54
52
  server: str = DEFAULT_COGAMES_API_SERVER,
55
- login_server: str = DEFAULT_COGAMES_SERVER,
56
53
  ) -> Any:
57
- with _create_client(token=token, server=server, login_server=login_server) as client:
54
+ with _create_client(token=token, server=server) as client:
58
55
  return client.login_player(player_id)
59
56
 
60
57
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: softmax-cli
3
- Version: 0.26.8
3
+ Version: 0.26.9
4
4
  Summary: Softmax CLI — authentication and account tools
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -146,12 +146,8 @@ def test_authenticate_reprompts_after_invalid_token(
146
146
  assert "Invalid token. Please try again." in capsys.readouterr().out
147
147
 
148
148
 
149
- def test_manual_command_includes_nondefault_login_server() -> None:
150
- assert _build_manual_set_token_command(login_server="https://softmax.com/api") == "softmax set-token '<TOKEN>'"
151
- assert (
152
- _build_manual_set_token_command(login_server="https://example.ngrok.app/api")
153
- == "softmax set-token '<TOKEN>' --login-server 'https://example.ngrok.app/api'"
154
- )
149
+ def test_manual_command_format() -> None:
150
+ assert _build_manual_set_token_command() == "softmax set-token '<TOKEN>'"
155
151
 
156
152
 
157
153
  def test_generic_authenticator_does_not_print_cogames_agent_hint(
@@ -74,10 +74,9 @@ def test_softmax_cogames_player_list_uses_expected_defaults(monkeypatch: pytest.
74
74
  captured: dict[str, object] = {}
75
75
 
76
76
  class FakeClient:
77
- def __init__(self, *, server_url: str, token: str, login_server: str) -> None:
77
+ def __init__(self, *, server_url: str, token: str) -> None:
78
78
  captured["server_url"] = server_url
79
79
  captured["token"] = token
80
- captured["login_server"] = login_server
81
80
 
82
81
  def __enter__(self) -> "FakeClient":
83
82
  return self
@@ -94,7 +93,6 @@ def test_softmax_cogames_player_list_uses_expected_defaults(monkeypatch: pytest.
94
93
  assert captured == {
95
94
  "server_url": "https://api.observatory.softmax-research.net",
96
95
  "token": "softmax-token",
97
- "login_server": "https://softmax.com/api",
98
96
  }
99
97
 
100
98
 
@@ -103,10 +101,9 @@ def test_softmax_cogames_login_returns_player_token(monkeypatch: pytest.MonkeyPa
103
101
  token = "player-token"
104
102
 
105
103
  class FakeClient:
106
- def __init__(self, *, server_url: str, token: str, login_server: str) -> None:
104
+ def __init__(self, *, server_url: str, token: str) -> None:
107
105
  self.server_url = server_url
108
106
  self.token = token
109
- self.login_server = login_server
110
107
 
111
108
  def __enter__(self) -> "FakeClient":
112
109
  return self
@@ -117,7 +114,6 @@ def test_softmax_cogames_login_returns_player_token(monkeypatch: pytest.MonkeyPa
117
114
  def login_player(self, player_id: str) -> FakeLoginResponse:
118
115
  assert self.server_url == "https://api.observatory.softmax-research.net"
119
116
  assert self.token == "softmax-token"
120
- assert self.login_server == "https://softmax.com/api"
121
117
  assert player_id == "ply_alpha"
122
118
  return FakeLoginResponse()
123
119
 
@@ -132,7 +128,7 @@ def test_softmax_cogames_login_response_returns_full_response(monkeypatch: pytes
132
128
  expires_at = "2026-02-21T12:00:00Z"
133
129
 
134
130
  class FakeClient:
135
- def __init__(self, *, server_url: str, token: str, login_server: str) -> None:
131
+ def __init__(self, *, server_url: str, token: str) -> None:
136
132
  pass
137
133
 
138
134
  def __enter__(self) -> "FakeClient":
File without changes
File without changes