lightnow-cli 1.4.0__tar.gz → 1.6.0__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 (52) hide show
  1. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/.github/workflows/ci.yml +6 -6
  2. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/.github/workflows/release-please.yml +1 -1
  3. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/.github/workflows/release.yml +11 -11
  4. lightnow_cli-1.6.0/.release-please-manifest.json +3 -0
  5. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/CHANGELOG.md +20 -0
  6. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/PKG-INFO +43 -2
  7. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/README.md +41 -0
  8. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/__init__.py +1 -1
  9. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/auth.py +70 -24
  10. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/integrations.py +4 -0
  11. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/config.py +217 -7
  12. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/main.py +4 -4
  13. lightnow_cli-1.6.0/lightnow_cli/target.py +102 -0
  14. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/pyproject.toml +1 -1
  15. lightnow_cli-1.6.0/renovate.json +47 -0
  16. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_auth_flow.py +4 -2
  17. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_cli.py +29 -28
  18. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_config.py +4 -2
  19. lightnow_cli-1.6.0/tests/test_connection_target.py +535 -0
  20. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_integrations.py +2 -0
  21. lightnow_cli-1.4.0/.release-please-manifest.json +0 -3
  22. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/.gitignore +0 -0
  23. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/CONTRIBUTING.md +0 -0
  24. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/LICENSE +0 -0
  25. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/Makefile +0 -0
  26. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/SECURITY.md +0 -0
  27. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/docs/development.md +0 -0
  28. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/docs/release.md +0 -0
  29. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/examples/README.md +0 -0
  30. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/examples/docs.md +0 -0
  31. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/examples/openapi.json +0 -0
  32. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/examples/server.json +0 -0
  33. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/authenticated_http.py +0 -0
  34. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/client.py +0 -0
  35. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/__init__.py +0 -0
  36. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/context.py +0 -0
  37. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/publish.py +0 -0
  38. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/query.py +0 -0
  39. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/runner.py +0 -0
  40. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/updates.py +0 -0
  41. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/commands/validate.py +0 -0
  42. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/tls.py +0 -0
  43. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/updates.py +0 -0
  44. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/lightnow_cli/validation.py +0 -0
  45. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/release-please-config.json +0 -0
  46. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/__init__.py +0 -0
  47. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_authenticated_http.py +0 -0
  48. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_client.py +0 -0
  49. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_context.py +0 -0
  50. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_tls.py +0 -0
  51. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_updates.py +0 -0
  52. {lightnow_cli-1.4.0 → lightnow_cli-1.6.0}/tests/test_validation.py +0 -0
@@ -23,10 +23,10 @@ jobs:
23
23
 
24
24
  steps:
25
25
  - name: Checkout
26
- uses: actions/checkout@v4
26
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
27
27
 
28
28
  - name: Set up Python
29
- uses: actions/setup-python@v5
29
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
30
30
  with:
31
31
  python-version: ${{ matrix.python-version }}
32
32
  cache: pip
@@ -59,12 +59,12 @@ jobs:
59
59
 
60
60
  steps:
61
61
  - name: Checkout
62
- uses: actions/checkout@v4
62
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
63
63
 
64
64
  - name: Set up Python
65
- uses: actions/setup-python@v5
65
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
66
66
  with:
67
- python-version: "3.13"
67
+ python-version: "3.12"
68
68
  cache: pip
69
69
 
70
70
  - name: Install build tools
@@ -74,7 +74,7 @@ jobs:
74
74
  run: make package
75
75
 
76
76
  - name: Upload distributions
77
- uses: actions/upload-artifact@v4
77
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
78
78
  with:
79
79
  name: python-distributions
80
80
  path: dist/*
@@ -17,7 +17,7 @@ jobs:
17
17
 
18
18
  steps:
19
19
  - name: Run Release Please
20
- uses: googleapis/release-please-action@v4
20
+ uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
21
21
  with:
22
22
  token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
23
23
  config-file: release-please-config.json
@@ -15,12 +15,12 @@ jobs:
15
15
 
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v4
18
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
19
19
 
20
20
  - name: Set up Python
21
- uses: actions/setup-python@v5
21
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
22
22
  with:
23
- python-version: "3.13"
23
+ python-version: "3.12"
24
24
  cache: pip
25
25
 
26
26
  - name: Install package
@@ -36,12 +36,12 @@ jobs:
36
36
 
37
37
  steps:
38
38
  - name: Checkout
39
- uses: actions/checkout@v4
39
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
40
40
 
41
41
  - name: Set up Python
42
- uses: actions/setup-python@v5
42
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
43
43
  with:
44
- python-version: "3.13"
44
+ python-version: "3.12"
45
45
  cache: pip
46
46
 
47
47
  - name: Install build tools
@@ -51,7 +51,7 @@ jobs:
51
51
  run: make package
52
52
 
53
53
  - name: Upload distributions
54
- uses: actions/upload-artifact@v4
54
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
55
55
  with:
56
56
  name: python-distributions
57
57
  path: dist/*
@@ -66,13 +66,13 @@ jobs:
66
66
 
67
67
  steps:
68
68
  - name: Download distributions
69
- uses: actions/download-artifact@v4
69
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
70
70
  with:
71
71
  name: python-distributions
72
72
  path: dist
73
73
 
74
74
  - name: Publish GitHub release notes
75
- uses: softprops/action-gh-release@v2
75
+ uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3
76
76
  with:
77
77
  generate_release_notes: true
78
78
  files: dist/*
@@ -89,13 +89,13 @@ jobs:
89
89
 
90
90
  steps:
91
91
  - name: Download distributions
92
- uses: actions/download-artifact@v4
92
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
93
93
  with:
94
94
  name: python-distributions
95
95
  path: dist
96
96
 
97
97
  - name: Publish distributions to PyPI
98
- uses: pypa/gh-action-pypi-publish@release/v1
98
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
99
99
 
100
100
  update-homebrew:
101
101
  name: Update Homebrew formula
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.6.0"
3
+ }
@@ -4,6 +4,26 @@ All notable changes to the LightNow CLI are documented here.
4
4
 
5
5
  This project follows semantic versioning.
6
6
 
7
+ ## [1.6.0](https://github.com/lightnow-ai/lightnow-cli/compare/v1.5.0...v1.6.0) (2026-10-09)
8
+
9
+
10
+ ### Features
11
+
12
+ * **auth:** support complete connection targets ([#39](https://github.com/lightnow-ai/lightnow-cli/issues/39)) ([699b09e](https://github.com/lightnow-ai/lightnow-cli/commit/699b09e70d1131a82667bb14a828d0234d19a75d))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **renovate:** constrain Python dependency names ([#22](https://github.com/lightnow-ai/lightnow-cli/issues/22)) ([a48bb79](https://github.com/lightnow-ai/lightnow-cli/commit/a48bb7935184d56f169c850d1fc478cde0c1667e))
18
+
19
+ ## [1.5.0](https://github.com/lightnow-ai/lightnow-cli/compare/v1.4.0...v1.5.0) (2026-07-18)
20
+
21
+
22
+ ### Features
23
+
24
+ * **#15:** configure runtime tool argument capture ([c83ff54](https://github.com/lightnow-ai/lightnow-cli/commit/c83ff54d4373bde152cca68a40f1c4e802e79e86))
25
+ * **sync:** configure tool argument capture ([09f591a](https://github.com/lightnow-ai/lightnow-cli/commit/09f591a94cb014998681223147a79ffcaff062e8))
26
+
7
27
  ## [1.4.0](https://github.com/lightnow-ai/lightnow-cli/compare/v1.3.1...v1.4.0) (2026-07-17)
8
28
 
9
29
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: lightnow-cli
3
- Version: 1.4.0
3
+ Version: 1.6.0
4
4
  Summary: Official LightNow CLI for MCP registry discovery, client config sync, and local MCP runner workflows
5
5
  Project-URL: Homepage, https://github.com/lightnow-ai/lightnow-cli
6
6
  Project-URL: Repository, https://github.com/lightnow-ai/lightnow-cli
@@ -121,6 +121,47 @@ your session with:
121
121
  lightnow status
122
122
  ```
123
123
 
124
+ For an isolated LightNow environment, obtain a credential-free connection target
125
+ from its operator and select it explicitly:
126
+
127
+ ```bash
128
+ lightnow login --target ./connection-target.json
129
+ ```
130
+
131
+ The complete JSON schema is:
132
+
133
+ ```json
134
+ {
135
+ "schemaVersion": 1,
136
+ "issuer": "https://auth.example.test/realms/example",
137
+ "clientId": "lightnow-cli",
138
+ "registryApiUrl": "https://registry.example.test/v0.1",
139
+ "adminApiUrl": "https://admin.example.test/v0/portal"
140
+ }
141
+ ```
142
+
143
+ All five fields are required; additional fields and embedded credentials are
144
+ rejected. URLs must use HTTPS without credentials, query strings or fragments.
145
+ The environment operator provisions the public OAuth device-flow client; the CLI
146
+ does not register clients. Authentication remains at the product issuer, which
147
+ may broker an external identity provider through its browser flow.
148
+ The device request includes `openid profile email organization:*`; the operator
149
+ must assign the standard `organization` client scope so existing organization
150
+ memberships are available to the scoped APIs.
151
+
152
+ `--target` cannot be combined with `--local`, `--issuer` or `--client-id`. Custom
153
+ issuers require a complete target and cannot silently use production API defaults.
154
+ Normal `lightnow login` and `lightnow login --local` remain available.
155
+
156
+ The selected target and named session are saved only after device authorization
157
+ and userinfo verification succeed. OIDC discovery must identify the selected
158
+ issuer and expose credential-free HTTPS endpoints. Failed login preserves the previous connection.
159
+ A different environment or account returns to personal context; logging into the
160
+ same target and account preserves the selected organization. Organization selection,
161
+ profile requests and generated Local Proxy connections use the selected API URLs.
162
+ For acceptance runs, use a separate container user/home and explicit client and
163
+ proxy configuration paths so existing desktop connections are not modified.
164
+
124
165
  ## Find MCP Servers
125
166
 
126
167
  Search the LightNow Registry:
@@ -73,6 +73,47 @@ your session with:
73
73
  lightnow status
74
74
  ```
75
75
 
76
+ For an isolated LightNow environment, obtain a credential-free connection target
77
+ from its operator and select it explicitly:
78
+
79
+ ```bash
80
+ lightnow login --target ./connection-target.json
81
+ ```
82
+
83
+ The complete JSON schema is:
84
+
85
+ ```json
86
+ {
87
+ "schemaVersion": 1,
88
+ "issuer": "https://auth.example.test/realms/example",
89
+ "clientId": "lightnow-cli",
90
+ "registryApiUrl": "https://registry.example.test/v0.1",
91
+ "adminApiUrl": "https://admin.example.test/v0/portal"
92
+ }
93
+ ```
94
+
95
+ All five fields are required; additional fields and embedded credentials are
96
+ rejected. URLs must use HTTPS without credentials, query strings or fragments.
97
+ The environment operator provisions the public OAuth device-flow client; the CLI
98
+ does not register clients. Authentication remains at the product issuer, which
99
+ may broker an external identity provider through its browser flow.
100
+ The device request includes `openid profile email organization:*`; the operator
101
+ must assign the standard `organization` client scope so existing organization
102
+ memberships are available to the scoped APIs.
103
+
104
+ `--target` cannot be combined with `--local`, `--issuer` or `--client-id`. Custom
105
+ issuers require a complete target and cannot silently use production API defaults.
106
+ Normal `lightnow login` and `lightnow login --local` remain available.
107
+
108
+ The selected target and named session are saved only after device authorization
109
+ and userinfo verification succeed. OIDC discovery must identify the selected
110
+ issuer and expose credential-free HTTPS endpoints. Failed login preserves the previous connection.
111
+ A different environment or account returns to personal context; logging into the
112
+ same target and account preserves the selected organization. Organization selection,
113
+ profile requests and generated Local Proxy connections use the selected API URLs.
114
+ For acceptance runs, use a separate container user/home and explicit client and
115
+ proxy configuration paths so existing desktop connections are not modified.
116
+
76
117
  ## Find MCP Servers
77
118
 
78
119
  Search the LightNow Registry:
@@ -1,3 +1,3 @@
1
1
  """LightNow CLI - A command-line tool for managing MCP servers."""
2
2
 
3
- __version__ = "1.4.0" # x-release-please-version
3
+ __version__ = "1.6.0" # x-release-please-version
@@ -7,6 +7,7 @@ import json
7
7
  import secrets
8
8
  import time
9
9
  import webbrowser
10
+ from pathlib import Path
10
11
  from typing import Any, Dict, Optional, cast
11
12
 
12
13
  import httpx
@@ -16,13 +17,16 @@ from rich.console import Console
16
17
  from typing_extensions import Annotated
17
18
 
18
19
  from ..config import (
20
+ DEFAULT_ADMIN_API_URL,
19
21
  DEFAULT_CLIENT_ID,
20
22
  DEFAULT_ISSUER,
23
+ DEFAULT_REGISTRY_API_URL,
21
24
  LOCAL_ADMIN_API_URL,
22
25
  LOCAL_ISSUER,
23
26
  LOCAL_REGISTRY_API_URL,
24
27
  config_manager,
25
28
  )
29
+ from ..target import ConnectionTarget, load_target, validate_https_url
26
30
 
27
31
  console = Console()
28
32
  app = typer.Typer(help="Authentication commands")
@@ -70,6 +74,10 @@ def pkce_challenge(verifier: str) -> str:
70
74
 
71
75
  async def discover_oidc_endpoints(issuer: str) -> Dict[str, str]:
72
76
  """Discover the OIDC endpoints required by the CLI."""
77
+ try:
78
+ issuer = validate_https_url(issuer)
79
+ except ValueError:
80
+ raise AuthError("OIDC issuer must be a credential-free HTTPS URL.") from None
73
81
  async with httpx.AsyncClient(timeout=AUTH_HTTP_TIMEOUT) as client:
74
82
  try:
75
83
  discovery_response = await client.get(
@@ -79,6 +87,10 @@ async def discover_oidc_endpoints(issuer: str) -> Dict[str, str]:
79
87
  discovery = discovery_response.json()
80
88
  if not isinstance(discovery, dict):
81
89
  raise AuthError("OIDC discovery response is not a JSON object")
90
+ if discovery.get("issuer") != issuer:
91
+ raise AuthError(
92
+ "OIDC discovery issuer does not match the selected issuer."
93
+ )
82
94
  except httpx.HTTPError as e:
83
95
  raise AuthError(
84
96
  f"Failed to discover OIDC endpoints: {describe_http_error(e)}"
@@ -91,12 +103,19 @@ async def discover_oidc_endpoints(issuer: str) -> Dict[str, str]:
91
103
  if not device_authorization_endpoint or not token_endpoint:
92
104
  raise AuthError("OIDC provider does not support device code flow")
93
105
 
94
- endpoints = {
95
- "device_authorization_endpoint": cast(str, device_authorization_endpoint),
96
- "token_endpoint": cast(str, token_endpoint),
97
- }
98
- if isinstance(userinfo_endpoint, str) and userinfo_endpoint:
99
- endpoints["userinfo_endpoint"] = userinfo_endpoint
106
+ try:
107
+ endpoints = {
108
+ "device_authorization_endpoint": validate_https_url(
109
+ device_authorization_endpoint
110
+ ),
111
+ "token_endpoint": validate_https_url(token_endpoint),
112
+ }
113
+ if userinfo_endpoint is not None:
114
+ endpoints["userinfo_endpoint"] = validate_https_url(userinfo_endpoint)
115
+ except ValueError:
116
+ raise AuthError(
117
+ "OIDC discovery endpoints must be credential-free HTTPS URLs."
118
+ ) from None
100
119
  return endpoints
101
120
 
102
121
 
@@ -111,7 +130,7 @@ async def device_code_flow(issuer: str, client_id: str) -> Dict[str, Any]:
111
130
  endpoints["device_authorization_endpoint"],
112
131
  data={
113
132
  "client_id": client_id,
114
- "scope": "openid profile email",
133
+ "scope": "openid profile email organization:*",
115
134
  "code_challenge": pkce_challenge(code_verifier),
116
135
  "code_challenge_method": "S256",
117
136
  },
@@ -295,6 +314,7 @@ def persist_refreshed_token(config: Any, token_data: Dict[str, Any]) -> str:
295
314
  else config.refresh_token
296
315
  ),
297
316
  None,
317
+ expected_binding=config_manager.connection_binding(config),
298
318
  )
299
319
  return refreshed_token
300
320
 
@@ -376,21 +396,43 @@ def login(
376
396
  hidden=True,
377
397
  ),
378
398
  ] = None,
399
+ target: Annotated[
400
+ Optional[Path],
401
+ typer.Option(
402
+ "--target",
403
+ help="Credential-free JSON connection target for an isolated environment.",
404
+ ),
405
+ ] = None,
379
406
  ) -> None:
380
407
  """Authenticate with LightNow."""
381
408
  try:
382
- resolved_issuer = issuer or (LOCAL_ISSUER if local else DEFAULT_ISSUER)
383
- resolved_client_id = client_id or DEFAULT_CLIENT_ID
384
- resolved_registry_api_url = LOCAL_REGISTRY_API_URL if local else None
385
- resolved_admin_api_url = LOCAL_ADMIN_API_URL if local else None
386
-
387
- # Store auth config
388
- config_manager.set_auth_config(
389
- resolved_issuer,
390
- resolved_client_id,
391
- resolved_registry_api_url,
392
- resolved_admin_api_url,
393
- )
409
+ if target is not None:
410
+ if local or issuer is not None or client_id is not None:
411
+ raise AuthError(
412
+ "--target cannot be combined with --local, --issuer or --client-id."
413
+ )
414
+ try:
415
+ connection = load_target(target)
416
+ except ValueError as exc:
417
+ raise AuthError(str(exc)) from None
418
+ else:
419
+ resolved_issuer = LOCAL_ISSUER if local else DEFAULT_ISSUER
420
+ if issuer is not None and issuer.rstrip("/") != resolved_issuer:
421
+ raise AuthError("A custom issuer requires --target with both API URLs.")
422
+ if client_id is not None and (
423
+ not client_id
424
+ or any(
425
+ character.isspace() or ord(character) < 32
426
+ for character in client_id
427
+ )
428
+ ):
429
+ raise AuthError("Client ID must be a nonempty identifier.")
430
+ connection = ConnectionTarget(
431
+ resolved_issuer,
432
+ client_id or DEFAULT_CLIENT_ID,
433
+ LOCAL_REGISTRY_API_URL if local else DEFAULT_REGISTRY_API_URL,
434
+ LOCAL_ADMIN_API_URL if local else DEFAULT_ADMIN_API_URL,
435
+ )
394
436
 
395
437
  # Perform device code flow
396
438
  if local:
@@ -399,23 +441,27 @@ def login(
399
441
  )
400
442
  else:
401
443
  console.print("[bold blue]Starting LightNow authentication...[/bold blue]")
402
- token_data = asyncio.run(device_code_flow(resolved_issuer, resolved_client_id))
444
+ token_data = asyncio.run(
445
+ device_code_flow(connection.issuer, connection.client_id)
446
+ )
403
447
  token = token_data.get("access_token")
404
448
  if not isinstance(token, str) or token == "":
405
449
  raise AuthError("OIDC token response did not include an access token.")
406
450
  refresh_token = token_data.get("refresh_token")
407
451
 
408
452
  # Fetch user info from the issuer instead of trusting local JWT claims.
409
- user_info = asyncio.run(fetch_user_info(resolved_issuer, token))
453
+ user_info = asyncio.run(fetch_user_info(connection.issuer, token))
454
+ subject = user_info.get("sub")
455
+ if not isinstance(subject, str) or not subject.strip():
456
+ raise AuthError("OIDC userinfo response did not include a valid subject.")
410
457
 
411
458
  # Store token and user info
412
- config_manager.set_token(
459
+ config_manager.commit_login(
460
+ connection,
413
461
  token,
414
462
  refresh_token if isinstance(refresh_token, str) else None,
415
463
  user_info,
416
- update_active_session=False,
417
464
  )
418
- config_manager.persist_current_session(user_info)
419
465
 
420
466
  console.print("[bold green]✓ Authentication successful![/bold green]")
421
467
  if user_info:
@@ -1214,11 +1214,15 @@ def build_local_proxy_config(
1214
1214
  ),
1215
1215
  "update_state_path": str(updates.UPDATE_STATE_PATH),
1216
1216
  "telemetry_enabled": True,
1217
+ "capture_tool_arguments": True,
1217
1218
  }
1218
1219
  if isinstance(local_proxy_settings, dict):
1219
1220
  local_proxy_config["telemetry_enabled"] = (
1220
1221
  local_proxy_settings.get("telemetryEnabled") is not False
1221
1222
  )
1223
+ local_proxy_config["capture_tool_arguments"] = (
1224
+ local_proxy_settings.get("captureToolArguments") is not False
1225
+ )
1222
1226
  local_proxy_config["allow_unmanaged_client_servers"] = (
1223
1227
  local_proxy_settings.get("allowUnmanagedClientServers") is not False
1224
1228
  )