lightnow-cli 1.3.1__tar.gz → 1.4.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 (49) hide show
  1. lightnow_cli-1.4.0/.release-please-manifest.json +3 -0
  2. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/CHANGELOG.md +13 -0
  3. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/PKG-INFO +23 -1
  4. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/README.md +21 -0
  5. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/docs/release.md +11 -4
  6. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/__init__.py +1 -1
  7. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/integrations.py +19 -0
  8. lightnow_cli-1.4.0/lightnow_cli/commands/updates.py +93 -0
  9. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/main.py +23 -3
  10. lightnow_cli-1.4.0/lightnow_cli/updates.py +382 -0
  11. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/pyproject.toml +2 -1
  12. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_cli.py +2 -0
  13. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_integrations.py +26 -1
  14. lightnow_cli-1.4.0/tests/test_updates.py +424 -0
  15. lightnow_cli-1.3.1/.release-please-manifest.json +0 -3
  16. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/.github/workflows/ci.yml +0 -0
  17. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/.github/workflows/release-please.yml +0 -0
  18. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/.github/workflows/release.yml +0 -0
  19. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/.gitignore +0 -0
  20. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/CONTRIBUTING.md +0 -0
  21. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/LICENSE +0 -0
  22. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/Makefile +0 -0
  23. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/SECURITY.md +0 -0
  24. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/docs/development.md +0 -0
  25. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/examples/README.md +0 -0
  26. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/examples/docs.md +0 -0
  27. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/examples/openapi.json +0 -0
  28. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/examples/server.json +0 -0
  29. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/authenticated_http.py +0 -0
  30. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/client.py +0 -0
  31. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/__init__.py +0 -0
  32. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/auth.py +0 -0
  33. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/context.py +0 -0
  34. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/publish.py +0 -0
  35. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/query.py +0 -0
  36. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/runner.py +0 -0
  37. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/commands/validate.py +0 -0
  38. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/config.py +0 -0
  39. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/tls.py +0 -0
  40. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/lightnow_cli/validation.py +0 -0
  41. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/release-please-config.json +0 -0
  42. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/__init__.py +0 -0
  43. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_auth_flow.py +0 -0
  44. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_authenticated_http.py +0 -0
  45. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_client.py +0 -0
  46. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_config.py +0 -0
  47. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_context.py +0 -0
  48. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_tls.py +0 -0
  49. {lightnow_cli-1.3.1 → lightnow_cli-1.4.0}/tests/test_validation.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.4.0"
3
+ }
@@ -4,6 +4,19 @@ All notable changes to the LightNow CLI are documented here.
4
4
 
5
5
  This project follows semantic versioning.
6
6
 
7
+ ## [1.4.0](https://github.com/lightnow-ai/lightnow-cli/compare/v1.3.1...v1.4.0) (2026-07-17)
8
+
9
+
10
+ ### Features
11
+
12
+ * **#13:** add managed updates ([844e9c5](https://github.com/lightnow-ai/lightnow-cli/commit/844e9c5cb0fb2134f9e6014f02a0a4ca49db019c))
13
+ * add managed updates ([275387e](https://github.com/lightnow-ai/lightnow-cli/commit/275387e05fd40f5b0564d9001acf9d3ed19ced5a))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * harden managed update checks ([0c3a285](https://github.com/lightnow-ai/lightnow-cli/commit/0c3a285ccdfab4b39e60f905c9274e242245b2d8))
19
+
7
20
  ## [1.3.1](https://github.com/lightnow-ai/lightnow-cli/compare/v1.3.0...v1.3.1) (2026-07-17)
8
21
 
9
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightnow-cli
3
- Version: 1.3.1
3
+ Version: 1.4.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
@@ -21,6 +21,7 @@ Requires-Python: >=3.11
21
21
  Requires-Dist: filelock>=3.16.1
22
22
  Requires-Dist: httpx>=0.24.0
23
23
  Requires-Dist: jsonschema>=4.17.0
24
+ Requires-Dist: packaging>=24.0
24
25
  Requires-Dist: pydantic>=2.0.0
25
26
  Requires-Dist: pyjwt>=2.8.0
26
27
  Requires-Dist: pyyaml>=6.0.0
@@ -88,6 +89,25 @@ If you prefer installing directly from the public repository:
88
89
  pipx install git+https://github.com/lightnow-ai/lightnow-cli.git
89
90
  ```
90
91
 
92
+ ## Update
93
+
94
+ The CLI manages supported installations of both the CLI and Local Proxy:
95
+
96
+ ```bash
97
+ lightnow update --check
98
+ lightnow update
99
+ ```
100
+
101
+ Use `lightnow update --yes` for an approved non-interactive update or
102
+ `lightnow update --check --json` for automation. Managed updates support
103
+ Homebrew, pipx and uv. Editable, plain pip and unknown virtual-environment
104
+ installs are reported but never changed automatically.
105
+
106
+ Interactive CLI sessions check the release catalog at most once per day and
107
+ show a short notice when an update is ready across all supported channels. Set
108
+ `LIGHTNOW_NO_UPDATE_CHECK=1` to disable this background check. The Local Proxy
109
+ never installs updates itself.
110
+
91
111
  ## Sign In
92
112
 
93
113
  ```bash
@@ -227,6 +247,8 @@ machine and its client-specific Runtime Profile in the LightNow Control Plane.
227
247
  The CLI stores a stable installation UUID in its protected configuration and a
228
248
  separate stable client-instance UUID in each generated proxy configuration. It
229
249
  does not report IP or MAC addresses, serial numbers, local paths, or secrets.
250
+ The Control Plane can additionally show the observed CLI and Proxy versions,
251
+ their supported installation channel and whether an update is available.
230
252
 
231
253
  For details, examples, diagrams and troubleshooting, see
232
254
  [Connect MCP clients](https://docs.lightnow.ai/getting-started/sync-mcp-clients).
@@ -41,6 +41,25 @@ If you prefer installing directly from the public repository:
41
41
  pipx install git+https://github.com/lightnow-ai/lightnow-cli.git
42
42
  ```
43
43
 
44
+ ## Update
45
+
46
+ The CLI manages supported installations of both the CLI and Local Proxy:
47
+
48
+ ```bash
49
+ lightnow update --check
50
+ lightnow update
51
+ ```
52
+
53
+ Use `lightnow update --yes` for an approved non-interactive update or
54
+ `lightnow update --check --json` for automation. Managed updates support
55
+ Homebrew, pipx and uv. Editable, plain pip and unknown virtual-environment
56
+ installs are reported but never changed automatically.
57
+
58
+ Interactive CLI sessions check the release catalog at most once per day and
59
+ show a short notice when an update is ready across all supported channels. Set
60
+ `LIGHTNOW_NO_UPDATE_CHECK=1` to disable this background check. The Local Proxy
61
+ never installs updates itself.
62
+
44
63
  ## Sign In
45
64
 
46
65
  ```bash
@@ -180,6 +199,8 @@ machine and its client-specific Runtime Profile in the LightNow Control Plane.
180
199
  The CLI stores a stable installation UUID in its protected configuration and a
181
200
  separate stable client-instance UUID in each generated proxy configuration. It
182
201
  does not report IP or MAC addresses, serial numbers, local paths, or secrets.
202
+ The Control Plane can additionally show the observed CLI and Proxy versions,
203
+ their supported installation channel and whether an update is available.
183
204
 
184
205
  For details, examples, diagrams and troubleshooting, see
185
206
  [Connect MCP clients](https://docs.lightnow.ai/getting-started/sync-mcp-clients).
@@ -38,7 +38,9 @@ Normal release flow:
38
38
  distributions through Trusted Publishing.
39
39
  6. After PyPI succeeds, the release workflow dispatches the exact published
40
40
  version to `lightnow-ai/homebrew-tap`. The tap regenerates, audits, installs,
41
- and tests the formula before committing it.
41
+ and tests the formula before committing it together with `releases.json`.
42
+ The public catalog advances only after the Homebrew artifact is verified,
43
+ so `lightnow update` never advertises a release that the tap cannot install.
42
44
 
43
45
  Manual release checklist for fallback or first-time setup:
44
46
 
@@ -70,9 +72,14 @@ Manual release checklist for fallback or first-time setup:
70
72
  - a GitHub Release is created with generated release notes and distribution artifacts,
71
73
  - PyPI publish succeeds through Trusted Publishing.
72
74
  8. Verify the `Update formula` workflow in `lightnow-ai/homebrew-tap`. For a
73
- recovery run, dispatch it manually with formula `lightnow-cli` and the exact
74
- already-published version; it uses the same generation and verification path
75
- as the automatic release dispatch.
75
+ recovery run, dispatch it manually with formula `lightnow-cli` and the exact
76
+ already-published version; it uses the same generation and verification path
77
+ as the automatic release dispatch.
78
+ 9. Verify the catalog and managed update path:
79
+ ```bash
80
+ curl -fsSL https://raw.githubusercontent.com/lightnow-ai/homebrew-tap/main/releases.json
81
+ lightnow update --check
82
+ ```
76
83
 
77
84
  Do not publish with long-lived local PyPI tokens.
78
85
 
@@ -1,3 +1,3 @@
1
1
  """LightNow CLI - A command-line tool for managing MCP servers."""
2
2
 
3
- __version__ = "1.3.1" # x-release-please-version
3
+ __version__ = "1.4.0" # x-release-please-version
@@ -23,6 +23,7 @@ import yaml
23
23
  from rich.console import Console
24
24
  from typing_extensions import Annotated
25
25
 
26
+ from .. import __version__, updates
26
27
  from ..authenticated_http import (
27
28
  authentication_error_from_response,
28
29
  request_with_refresh,
@@ -1207,6 +1208,11 @@ def build_local_proxy_config(
1207
1208
  "client_instance_id": client_instance_id,
1208
1209
  "device_hostname": socket.gethostname(),
1209
1210
  "device_platform": runtime_device_platform(),
1211
+ "cli_version": __version__,
1212
+ "cli_install_method": updates.detect_install_method(
1213
+ "lightnow-cli", shutil.which("lightnow") or sys.argv[0]
1214
+ ),
1215
+ "update_state_path": str(updates.UPDATE_STATE_PATH),
1210
1216
  "telemetry_enabled": True,
1211
1217
  }
1212
1218
  if isinstance(local_proxy_settings, dict):
@@ -1298,6 +1304,13 @@ def register_runtime_device_client(
1298
1304
  "device": {
1299
1305
  "reported_hostname": socket.gethostname(),
1300
1306
  "platform": runtime_device_platform(),
1307
+ "cli_version": __version__,
1308
+ "cli_install_method": updates.detect_install_method(
1309
+ "lightnow-cli", shutil.which("lightnow") or sys.argv[0]
1310
+ ),
1311
+ "cli_latest_version": None,
1312
+ "cli_update_status": "unknown",
1313
+ "cli_update_checked_at": None,
1301
1314
  },
1302
1315
  "client": {
1303
1316
  "name": client,
@@ -1305,6 +1318,12 @@ def register_runtime_device_client(
1305
1318
  "profile": profile,
1306
1319
  "runner_name": "lightnow-local-proxy",
1307
1320
  "runner_version": None,
1321
+ "runner_install_method": updates.detect_install_method(
1322
+ "lightnow-proxy", local_proxy_command()
1323
+ ),
1324
+ "runner_latest_version": None,
1325
+ "runner_update_status": "unknown",
1326
+ "runner_update_checked_at": None,
1308
1327
  "transport": transport,
1309
1328
  },
1310
1329
  },
@@ -0,0 +1,93 @@
1
+ """User-facing managed update commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json as json_module
6
+ from pathlib import Path
7
+ from typing import Annotated
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+
13
+ from lightnow_cli import updates
14
+
15
+ console = Console()
16
+
17
+
18
+ def _render(state: dict, json_output: bool) -> None:
19
+ if json_output:
20
+ typer.echo(json_module.dumps(state, indent=2))
21
+ return
22
+ table = Table(title="LightNow updates")
23
+ table.add_column("Component")
24
+ table.add_column("Installed")
25
+ table.add_column("Latest")
26
+ table.add_column("Installer")
27
+ table.add_column("Status")
28
+ table.add_column("Result")
29
+ for package, entry in state.get("packages", {}).items():
30
+ table.add_row(
31
+ package,
32
+ entry.get("installed_version") or "not installed",
33
+ entry.get("latest_version") or "unknown",
34
+ entry.get("install_method") or "unknown",
35
+ entry.get("status") or "unknown",
36
+ entry.get("result") or "checked",
37
+ )
38
+ if entry.get("error"):
39
+ table.add_row("", "", "", "", "", f"[red]{entry['error']}[/red]")
40
+ console.print(table)
41
+
42
+
43
+ def update(
44
+ yes: Annotated[
45
+ bool, typer.Option("--yes", "-y", help="Apply updates without prompting.")
46
+ ] = False,
47
+ check: Annotated[
48
+ bool, typer.Option("--check", help="Check only; do not install updates.")
49
+ ] = False,
50
+ json_output: Annotated[
51
+ bool, typer.Option("--json", help="Print machine-readable JSON.")
52
+ ] = False,
53
+ ) -> None:
54
+ """Check for and apply LightNow CLI and Local Proxy updates."""
55
+ if check and yes:
56
+ raise typer.BadParameter("--yes cannot be combined with --check")
57
+ if json_output and not check and not yes:
58
+ raise typer.BadParameter(
59
+ "--json requires --check or --yes to avoid an interactive prompt"
60
+ )
61
+ try:
62
+ state = updates.refresh_update_state()
63
+ except Exception as error:
64
+ if json_output:
65
+ typer.echo(
66
+ json_module.dumps({"error": str(error), "packages": {}}, indent=2)
67
+ )
68
+ else:
69
+ console.print(f"[red]Could not check for LightNow updates:[/red] {error}")
70
+ raise typer.Exit(1) from error
71
+
72
+ outdated = updates.cached_outdated_packages(state)
73
+ if check or not outdated:
74
+ _render(state, json_output)
75
+ return
76
+ if not yes and not typer.confirm(
77
+ f"Update {len(outdated)} LightNow component(s) now?"
78
+ ):
79
+ _render(state, json_output)
80
+ return
81
+
82
+ state, success = updates.apply_updates(state)
83
+ _render(state, json_output)
84
+ if not success:
85
+ raise typer.Exit(1)
86
+
87
+
88
+ def refresh_update_state() -> None:
89
+ """Refresh update state in a detached helper process."""
90
+ try:
91
+ updates.refresh_update_state()
92
+ finally:
93
+ Path(updates.UPDATE_PENDING_PATH).unlink(missing_ok=True)
@@ -3,8 +3,19 @@
3
3
  import typer
4
4
  from typing_extensions import Annotated
5
5
 
6
- from . import __version__
7
- from .commands import auth, context, integrations, publish, query, runner, validate
6
+ from . import __version__, updates
7
+ from .commands import (
8
+ auth,
9
+ context,
10
+ integrations,
11
+ publish,
12
+ query,
13
+ runner,
14
+ )
15
+ from .commands import updates as update_commands
16
+ from .commands import (
17
+ validate,
18
+ )
8
19
  from .tls import configure_tls_trust_store
9
20
 
10
21
  configure_tls_trust_store()
@@ -29,6 +40,8 @@ app.command("import-config")(integrations.import_config)
29
40
  app.command("sync")(integrations.sync)
30
41
  app.command("config-status")(integrations.config_status)
31
42
  app.command("run")(runner.run)
43
+ app.command("update")(update_commands.update)
44
+ app.command("_refresh-update-state", hidden=True)(update_commands.refresh_update_state)
32
45
 
33
46
 
34
47
  def version_callback(value: bool) -> None:
@@ -45,7 +58,14 @@ def main(
45
58
  ] = False,
46
59
  ) -> None:
47
60
  """LightNow CLI."""
48
- pass
61
+ if updates.should_check_automatically():
62
+ outdated = updates.cached_outdated_packages(updates.read_update_state())
63
+ if outdated:
64
+ typer.echo(
65
+ f"LightNow update available for {', '.join(outdated)}. Run `lightnow update`.",
66
+ err=True,
67
+ )
68
+ updates.start_background_refresh()
49
69
 
50
70
 
51
71
  def cli() -> None: