licos-platform-cli 0.2.13__tar.gz → 0.2.14__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: licos-platform-cli
3
- Version: 0.2.13
3
+ Version: 0.2.14
4
4
  Summary: LICOS platform CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: licos-platform-sdk>=0.3.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "licos-platform-cli"
7
- version = "0.2.13"
7
+ version = "0.2.14"
8
8
  description = "LICOS platform CLI"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -483,10 +483,10 @@ OAUTH_HELP = {
483
483
  {"name": "--auto-apply / --app-id", "required": True, "description": "Explicit application selection mode."},
484
484
  {"name": "--name", "required": True, "description": "Application display name."},
485
485
  {"name": "--login-audience", "required": True, "description": "ALL or OWNER_TENANT."},
486
- {"name": "--redirect", "required": False, "description": "Optional explicit callback override; otherwise derive stable dev/prod callbacks from project runtime metadata."},
487
- {"name": "--public-base-url", "required": False, "description": "Optional public LICOS OAuth2 origin override."},
486
+ {"name": "--redirect", "required": False, "description": "Optional explicit callback override; otherwise derive stable dev/prod callbacks from project runtime metadata."},
487
+ {"name": "--public-base-url", "required": False, "description": "Optional public LICOS OAuth2 origin override."},
488
488
  ],
489
- "example": "licos-platform oauth configure-project --auto-apply --name 'Project login' --login-audience ALL",
489
+ "example": "licos-platform oauth configure-project --auto-apply --name 'Project login' --login-audience ALL",
490
490
  },
491
491
  "delete": {"description": "Delete an OAuth2 application.", "parameters": [], "example": "licos-platform oauth delete app-id"},
492
492
  "enable": {"description": "Enable an OAuth2 application.", "parameters": [], "example": "licos-platform oauth enable app-id"},
@@ -5,7 +5,7 @@ import json
5
5
  import os
6
6
  from pathlib import Path
7
7
  from typing import Any, Callable
8
- from urllib.parse import quote, urlsplit, urlunsplit
8
+ from urllib.parse import quote, urlsplit, urlunsplit
9
9
 
10
10
  from licos_platform_sdk import oauth
11
11
 
@@ -43,43 +43,43 @@ def _parse_redirect(value: str) -> dict[str, str]:
43
43
  }
44
44
 
45
45
 
46
- def _public_base_url(value: str) -> str:
46
+ def _public_base_url(value: str) -> str:
47
47
  parsed = urlsplit(_parse_http_url(value, option="--public-base-url"))
48
48
  path = parsed.path.rstrip("/")
49
49
  if path.endswith("/api/v1"):
50
50
  path = path[: -len("/api/v1")]
51
- return urlunsplit((parsed.scheme, parsed.netloc, path, "", "")).rstrip("/")
52
-
53
-
54
- def _required_env(name: str) -> str:
55
- value = (os.environ.get(name) or "").strip()
56
- if not value:
57
- raise argparse.ArgumentTypeError(
58
- f"{name} is required when --redirect is not provided"
59
- )
60
- return value
61
-
62
-
63
- def _automatic_redirects() -> list[dict[str, str]]:
64
- project_id = _required_env("AGENT_PROJECT_ID")
65
- encoded_project_id = quote(project_id, safe="")
66
- platform_public_base = _public_base_url(_required_env("LICOS_PLATFORM_PUBLIC_BASE_URL"))
67
- deploy_public_base = _public_base_url(_required_env("LICOS_DEPLOY_PUBLIC_BASE_URL"))
68
- return [
69
- {
70
- "environment": "dev",
71
- "redirectUri": (
72
- f"{platform_public_base}/agent/api/v1/projects/"
73
- f"{encoded_project_id}/oauth/callback"
74
- ),
75
- },
76
- {
77
- "environment": "prod",
78
- "redirectUri": (
79
- f"{deploy_public_base}/p/{encoded_project_id}/api/auth/licos/callback"
80
- ),
81
- },
82
- ]
51
+ return urlunsplit((parsed.scheme, parsed.netloc, path, "", "")).rstrip("/")
52
+
53
+
54
+ def _required_env(name: str) -> str:
55
+ value = (os.environ.get(name) or "").strip()
56
+ if not value:
57
+ raise argparse.ArgumentTypeError(
58
+ f"{name} is required when --redirect is not provided"
59
+ )
60
+ return value
61
+
62
+
63
+ def _automatic_redirects() -> list[dict[str, str]]:
64
+ project_id = _required_env("AGENT_PROJECT_ID")
65
+ encoded_project_id = quote(project_id, safe="")
66
+ platform_public_base = _public_base_url(_required_env("LICOS_PLATFORM_PUBLIC_BASE_URL"))
67
+ deploy_public_base = _public_base_url(_required_env("LICOS_DEPLOY_PUBLIC_BASE_URL"))
68
+ return [
69
+ {
70
+ "environment": "dev",
71
+ "redirectUri": (
72
+ f"{platform_public_base}/agent-gateway/api/v1/projects/"
73
+ f"{encoded_project_id}/oauth/callback"
74
+ ),
75
+ },
76
+ {
77
+ "environment": "prod",
78
+ "redirectUri": (
79
+ f"{deploy_public_base}/p/{encoded_project_id}/api/auth/licos/callback"
80
+ ),
81
+ },
82
+ ]
83
83
 
84
84
 
85
85
  def _project_root() -> Path:
@@ -137,25 +137,25 @@ def _cmd_revoke_grant(args: argparse.Namespace) -> Any:
137
137
  return oauth.revoke_grant(args.grant_id)
138
138
 
139
139
 
140
- def _cmd_configure_project(args: argparse.Namespace) -> dict[str, Any]:
141
- public_base_url = (
142
- args.public_base_url
143
- or os.environ.get("LICOS_PLATFORM_PUBLIC_BASE_URL")
144
- or os.environ.get("LICOS_OAUTH_PUBLIC_BASE_URL")
145
- )
146
- if not public_base_url:
147
- raise argparse.ArgumentTypeError(
148
- "--public-base-url, LICOS_PLATFORM_PUBLIC_BASE_URL, or LICOS_OAUTH_PUBLIC_BASE_URL is required"
149
- )
150
- normalized_public_base_url = _public_base_url(public_base_url)
151
- redirects = args.redirects or _automatic_redirects()
152
- path = _config_path(args.out)
140
+ def _cmd_configure_project(args: argparse.Namespace) -> dict[str, Any]:
141
+ public_base_url = (
142
+ args.public_base_url
143
+ or os.environ.get("LICOS_PLATFORM_PUBLIC_BASE_URL")
144
+ or os.environ.get("LICOS_OAUTH_PUBLIC_BASE_URL")
145
+ )
146
+ if not public_base_url:
147
+ raise argparse.ArgumentTypeError(
148
+ "--public-base-url, LICOS_PLATFORM_PUBLIC_BASE_URL, or LICOS_OAUTH_PUBLIC_BASE_URL is required"
149
+ )
150
+ normalized_public_base_url = _public_base_url(public_base_url)
151
+ redirects = args.redirects or _automatic_redirects()
152
+ path = _config_path(args.out)
153
153
  app = oauth.ensure_project_app(
154
154
  app_id=args.app_id,
155
155
  name=args.name,
156
156
  description=args.description,
157
157
  login_audience=args.login_audience,
158
- redirect_uri_configs=redirects,
158
+ redirect_uri_configs=redirects,
159
159
  allowed_scopes=args.scopes or DEFAULT_SCOPES,
160
160
  )
161
161
  app_id = str(app.get("id") or "").strip()
@@ -172,7 +172,7 @@ def _cmd_configure_project(args: argparse.Namespace) -> dict[str, Any]:
172
172
  "clientId": client_id,
173
173
  "loginAudience": args.login_audience,
174
174
  "scopes": args.scopes or DEFAULT_SCOPES,
175
- "redirectUriConfigs": redirects,
175
+ "redirectUriConfigs": redirects,
176
176
  "pkce": True,
177
177
  "publicBaseUrl": normalized_public_base_url,
178
178
  "endpoints": {
@@ -218,11 +218,11 @@ def add_oauth_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentPar
218
218
  configure.add_argument("--login-audience", required=True, choices=["ALL", "OWNER_TENANT"])
219
219
  configure.add_argument(
220
220
  "--redirect",
221
- action="append",
222
- dest="redirects",
223
- type=_parse_redirect,
224
- default=None,
225
- help="Explicit callback override as dev=URL, prod=URL, or URL (defaults to prod); can repeat",
221
+ action="append",
222
+ dest="redirects",
223
+ type=_parse_redirect,
224
+ default=None,
225
+ help="Explicit callback override as dev=URL, prod=URL, or URL (defaults to prod); can repeat",
226
226
  )
227
227
  configure.add_argument("--scope", action="append", dest="scopes", default=None, help="OAuth2 scope; can repeat")
228
228
  configure.add_argument("--public-base-url", default=None, help="Public LICOS OAuth2 origin; /api/v1 is removed")
@@ -251,7 +251,7 @@ class PlatformCliTests(unittest.TestCase):
251
251
  allowed_scopes=["openid", "profile", "email"],
252
252
  )
253
253
 
254
- def test_oauth_configure_auto_apply_uses_environment_public_base(self) -> None:
254
+ def test_oauth_configure_auto_apply_uses_environment_public_base(self) -> None:
255
255
  app = {
256
256
  "id": "app-2",
257
257
  "appCode": "project-project-1",
@@ -281,84 +281,84 @@ class PlatformCliTests(unittest.TestCase):
281
281
  config = json.loads((Path(tmp) / "config" / "licos-oauth.json").read_text(encoding="utf-8"))
282
282
  self.assertEqual(config["publicBaseUrl"], "https://platform.example")
283
283
  self.assertEqual(config["redirectUriConfigs"][0]["environment"], "prod")
284
- self.assertIsNone(ensure.call_args.kwargs["app_id"])
285
- self.assertEqual(json.loads(stdout)["selectionMode"], "auto-apply")
286
-
287
- def test_oauth_configure_auto_generates_stable_project_callbacks(self) -> None:
288
- app = {
289
- "id": "app-3",
290
- "appCode": "project-project-1",
291
- "clientId": "client-3",
292
- }
293
- with tempfile.TemporaryDirectory() as tmp:
294
- env = {
295
- "LICOS_PROJECT_PATH": tmp,
296
- "AGENT_PROJECT_ID": "project/1",
297
- "LICOS_PLATFORM_PUBLIC_BASE_URL": "https://platform.example",
298
- "LICOS_DEPLOY_PUBLIC_BASE_URL": "https://deploy.example",
299
- }
300
- with mock.patch.dict(os.environ, env, clear=False):
301
- with mock.patch.object(oauth_commands.oauth, "ensure_project_app", return_value=app) as ensure:
302
- code, stdout, stderr = run_cli(
303
- "oauth",
304
- "configure-project",
305
- "--auto-apply",
306
- "--name",
307
- "Project login",
308
- "--login-audience",
309
- "ALL",
310
- )
311
-
312
- self.assertEqual(code, 0)
313
- self.assertEqual(stderr, "")
314
- redirects = ensure.call_args.kwargs["redirect_uri_configs"]
315
- self.assertEqual(
316
- redirects,
317
- [
318
- {
319
- "environment": "dev",
320
- "redirectUri": (
321
- "https://platform.example/agent/api/v1/projects/"
322
- "project%2F1/oauth/callback"
323
- ),
324
- },
325
- {
326
- "environment": "prod",
327
- "redirectUri": (
328
- "https://deploy.example/p/project%2F1/api/auth/licos/callback"
329
- ),
330
- },
331
- ],
332
- )
333
- config = json.loads((Path(tmp) / "config" / "licos-oauth.json").read_text(encoding="utf-8"))
334
- self.assertEqual(config["redirectUriConfigs"], redirects)
335
- self.assertEqual(config["publicBaseUrl"], "https://platform.example")
336
- self.assertEqual(json.loads(stdout)["selectionMode"], "auto-apply")
337
-
338
- def test_oauth_configure_without_redirect_requires_stable_project_metadata(self) -> None:
339
- with tempfile.TemporaryDirectory() as tmp:
340
- env = {
341
- "LICOS_PROJECT_PATH": tmp,
342
- "LICOS_PLATFORM_PUBLIC_BASE_URL": "https://platform.example",
343
- "LICOS_DEPLOY_PUBLIC_BASE_URL": "https://deploy.example",
344
- "AGENT_PROJECT_ID": "",
345
- }
346
- with mock.patch.dict(os.environ, env, clear=False):
347
- with mock.patch.object(oauth_commands.oauth, "ensure_project_app") as ensure:
348
- code, stdout, stderr = run_cli(
349
- "oauth",
350
- "configure-project",
351
- "--auto-apply",
352
- "--name",
353
- "Project login",
354
- "--login-audience",
355
- "ALL",
356
- )
357
-
358
- self.assertEqual(code, 2)
359
- self.assertEqual(stdout, "")
360
- self.assertIn("AGENT_PROJECT_ID", stderr)
361
- ensure.assert_not_called()
284
+ self.assertIsNone(ensure.call_args.kwargs["app_id"])
285
+ self.assertEqual(json.loads(stdout)["selectionMode"], "auto-apply")
286
+
287
+ def test_oauth_configure_auto_generates_stable_project_callbacks(self) -> None:
288
+ app = {
289
+ "id": "app-3",
290
+ "appCode": "project-project-1",
291
+ "clientId": "client-3",
292
+ }
293
+ with tempfile.TemporaryDirectory() as tmp:
294
+ env = {
295
+ "LICOS_PROJECT_PATH": tmp,
296
+ "AGENT_PROJECT_ID": "project/1",
297
+ "LICOS_PLATFORM_PUBLIC_BASE_URL": "https://platform.example",
298
+ "LICOS_DEPLOY_PUBLIC_BASE_URL": "https://deploy.example",
299
+ }
300
+ with mock.patch.dict(os.environ, env, clear=False):
301
+ with mock.patch.object(oauth_commands.oauth, "ensure_project_app", return_value=app) as ensure:
302
+ code, stdout, stderr = run_cli(
303
+ "oauth",
304
+ "configure-project",
305
+ "--auto-apply",
306
+ "--name",
307
+ "Project login",
308
+ "--login-audience",
309
+ "ALL",
310
+ )
311
+
312
+ self.assertEqual(code, 0)
313
+ self.assertEqual(stderr, "")
314
+ redirects = ensure.call_args.kwargs["redirect_uri_configs"]
315
+ self.assertEqual(
316
+ redirects,
317
+ [
318
+ {
319
+ "environment": "dev",
320
+ "redirectUri": (
321
+ "https://platform.example/agent-gateway/api/v1/projects/"
322
+ "project%2F1/oauth/callback"
323
+ ),
324
+ },
325
+ {
326
+ "environment": "prod",
327
+ "redirectUri": (
328
+ "https://deploy.example/p/project%2F1/api/auth/licos/callback"
329
+ ),
330
+ },
331
+ ],
332
+ )
333
+ config = json.loads((Path(tmp) / "config" / "licos-oauth.json").read_text(encoding="utf-8"))
334
+ self.assertEqual(config["redirectUriConfigs"], redirects)
335
+ self.assertEqual(config["publicBaseUrl"], "https://platform.example")
336
+ self.assertEqual(json.loads(stdout)["selectionMode"], "auto-apply")
337
+
338
+ def test_oauth_configure_without_redirect_requires_stable_project_metadata(self) -> None:
339
+ with tempfile.TemporaryDirectory() as tmp:
340
+ env = {
341
+ "LICOS_PROJECT_PATH": tmp,
342
+ "LICOS_PLATFORM_PUBLIC_BASE_URL": "https://platform.example",
343
+ "LICOS_DEPLOY_PUBLIC_BASE_URL": "https://deploy.example",
344
+ "AGENT_PROJECT_ID": "",
345
+ }
346
+ with mock.patch.dict(os.environ, env, clear=False):
347
+ with mock.patch.object(oauth_commands.oauth, "ensure_project_app") as ensure:
348
+ code, stdout, stderr = run_cli(
349
+ "oauth",
350
+ "configure-project",
351
+ "--auto-apply",
352
+ "--name",
353
+ "Project login",
354
+ "--login-audience",
355
+ "ALL",
356
+ )
357
+
358
+ self.assertEqual(code, 2)
359
+ self.assertEqual(stdout, "")
360
+ self.assertIn("AGENT_PROJECT_ID", stderr)
361
+ ensure.assert_not_called()
362
362
 
363
363
  def test_oauth_invalid_output_path_does_not_mutate_platform(self) -> None:
364
364
  with tempfile.TemporaryDirectory() as tmp: