licos-platform-cli 0.2.13__tar.gz → 0.2.15__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.
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/PKG-INFO +2 -2
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/pyproject.toml +2 -2
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/help_commands.py +3 -3
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/oauth_commands.py +48 -48
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/tests/test_cli.py +71 -71
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/.gitignore +0 -0
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/__init__.py +0 -0
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/database_commands.py +0 -0
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/knowledge_commands.py +0 -0
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/main.py +0 -0
- {licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/storage_commands.py +0 -0
|
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "licos-platform-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.15"
|
|
8
8
|
description = "LICOS platform CLI"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"licos-platform-sdk>=0.3.
|
|
11
|
+
"licos-platform-sdk>=0.3.2",
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
[project.scripts]
|
{licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/help_commands.py
RENAMED
|
@@ -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"},
|
{licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/oauth_commands.py
RENAMED
|
@@ -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:
|
|
@@ -143,19 +143,19 @@ def _cmd_configure_project(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
143
143
|
or os.environ.get("LICOS_PLATFORM_PUBLIC_BASE_URL")
|
|
144
144
|
or os.environ.get("LICOS_OAUTH_PUBLIC_BASE_URL")
|
|
145
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)
|
|
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:
|
|
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
294
|
env = {
|
|
295
295
|
"LICOS_PROJECT_PATH": tmp,
|
|
296
296
|
"AGENT_PROJECT_ID": "project/1",
|
|
297
297
|
"LICOS_PLATFORM_PUBLIC_BASE_URL": "https://platform.example",
|
|
298
298
|
"LICOS_DEPLOY_PUBLIC_BASE_URL": "https://deploy.example",
|
|
299
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)
|
|
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
335
|
self.assertEqual(config["publicBaseUrl"], "https://platform.example")
|
|
336
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()
|
|
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:
|
|
File without changes
|
|
File without changes
|
{licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/database_commands.py
RENAMED
|
File without changes
|
{licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/knowledge_commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{licos_platform_cli-0.2.13 → licos_platform_cli-0.2.15}/src/licos_platform_cli/storage_commands.py
RENAMED
|
File without changes
|