lightnow-cli 1.3.0__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.3.0 → lightnow_cli-1.4.0}/.github/workflows/release.yml +24 -0
  2. lightnow_cli-1.4.0/.release-please-manifest.json +3 -0
  3. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/CHANGELOG.md +20 -0
  4. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/PKG-INFO +23 -1
  5. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/README.md +21 -0
  6. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/docs/release.md +17 -6
  7. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/__init__.py +1 -1
  8. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/integrations.py +31 -0
  9. lightnow_cli-1.4.0/lightnow_cli/commands/updates.py +93 -0
  10. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/main.py +23 -3
  11. lightnow_cli-1.4.0/lightnow_cli/updates.py +382 -0
  12. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/pyproject.toml +2 -1
  13. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_cli.py +2 -0
  14. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_integrations.py +71 -1
  15. lightnow_cli-1.4.0/tests/test_updates.py +424 -0
  16. lightnow_cli-1.3.0/.release-please-manifest.json +0 -3
  17. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/.github/workflows/ci.yml +0 -0
  18. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/.github/workflows/release-please.yml +0 -0
  19. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/.gitignore +0 -0
  20. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/CONTRIBUTING.md +0 -0
  21. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/LICENSE +0 -0
  22. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/Makefile +0 -0
  23. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/SECURITY.md +0 -0
  24. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/docs/development.md +0 -0
  25. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/examples/README.md +0 -0
  26. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/examples/docs.md +0 -0
  27. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/examples/openapi.json +0 -0
  28. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/examples/server.json +0 -0
  29. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/authenticated_http.py +0 -0
  30. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/client.py +0 -0
  31. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/__init__.py +0 -0
  32. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/auth.py +0 -0
  33. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/context.py +0 -0
  34. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/publish.py +0 -0
  35. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/query.py +0 -0
  36. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/runner.py +0 -0
  37. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/commands/validate.py +0 -0
  38. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/config.py +0 -0
  39. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/tls.py +0 -0
  40. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/lightnow_cli/validation.py +0 -0
  41. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/release-please-config.json +0 -0
  42. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/__init__.py +0 -0
  43. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_auth_flow.py +0 -0
  44. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_authenticated_http.py +0 -0
  45. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_client.py +0 -0
  46. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_config.py +0 -0
  47. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_context.py +0 -0
  48. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_tls.py +0 -0
  49. {lightnow_cli-1.3.0 → lightnow_cli-1.4.0}/tests/test_validation.py +0 -0
@@ -96,3 +96,27 @@ jobs:
96
96
 
97
97
  - name: Publish distributions to PyPI
98
98
  uses: pypa/gh-action-pypi-publish@release/v1
99
+
100
+ update-homebrew:
101
+ name: Update Homebrew formula
102
+ runs-on: ubuntu-latest
103
+ needs: publish-pypi
104
+
105
+ steps:
106
+ - name: Dispatch published version to Homebrew tap
107
+ env:
108
+ GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
109
+ TAG_NAME: ${{ github.ref_name }}
110
+ run: |
111
+ version="${TAG_NAME#v}"
112
+ payload="$(jq -n \
113
+ --arg formula "lightnow-cli" \
114
+ --arg version "$version" \
115
+ '{event_type: "python-release-published", client_payload: {formula: $formula, version: $version}}')"
116
+ curl --fail-with-body --silent --show-error \
117
+ --request POST \
118
+ --header "Accept: application/vnd.github+json" \
119
+ --header "Authorization: Bearer ${GH_TOKEN}" \
120
+ --header "X-GitHub-Api-Version: 2022-11-28" \
121
+ https://api.github.com/repos/lightnow-ai/homebrew-tap/dispatches \
122
+ --data "$payload"
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.4.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.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
+
20
+ ## [1.3.1](https://github.com/lightnow-ai/lightnow-cli/compare/v1.3.0...v1.3.1) (2026-07-17)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **sync:** replace existing codex proxy connection ([c09b7f7](https://github.com/lightnow-ai/lightnow-cli/commit/c09b7f75f9d78c943ed7556b10b10ff99dbb2008))
26
+
7
27
  ## [1.3.0](https://github.com/lightnow-ai/lightnow-cli/compare/v1.2.0...v1.3.0) (2026-07-15)
8
28
 
9
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightnow-cli
3
- Version: 1.3.0
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).
@@ -19,6 +19,9 @@ Required GitHub setup:
19
19
  - Repository secret: `RELEASE_PLEASE_TOKEN`
20
20
  - Token permissions: contents, pull requests, and issues read/write for this
21
21
  repository
22
+ - Repository secret: `HOMEBREW_TAP_TOKEN`
23
+ - Token permissions: contents read/write for `lightnow-ai/homebrew-tap`; the
24
+ token is used only to send the post-PyPI repository dispatch
22
25
  - Repository Actions setting: allow GitHub Actions to create pull requests
23
26
 
24
27
  Use a real PAT or GitHub App token for `RELEASE_PLEASE_TOKEN`, not the default
@@ -33,7 +36,11 @@ Normal release flow:
33
36
  4. Release Please creates the `vX.Y.Z` tag and GitHub Release.
34
37
  5. The existing tag-based `Release` workflow builds and publishes PyPI
35
38
  distributions through Trusted Publishing.
36
- 6. Update the Homebrew tap after the release artifact is available.
39
+ 6. After PyPI succeeds, the release workflow dispatches the exact published
40
+ version to `lightnow-ai/homebrew-tap`. The tap regenerates, audits, installs,
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.
37
44
 
38
45
  Manual release checklist for fallback or first-time setup:
39
46
 
@@ -64,11 +71,15 @@ Manual release checklist for fallback or first-time setup:
64
71
  - `twine check` passes,
65
72
  - a GitHub Release is created with generated release notes and distribution artifacts,
66
73
  - PyPI publish succeeds through Trusted Publishing.
67
- 8. Update the Homebrew tap after the release artifact is available:
68
- - repository: `lightnow-ai/homebrew-tap`
69
- - formula: `Formula/lightnow-cli.rb`
70
- - replace the formula template URL, version and SHA256 with the released artifact,
71
- - run `brew audit`, `brew install --build-from-source`, and `brew test`.
74
+ 8. Verify the `Update formula` workflow in `lightnow-ai/homebrew-tap`. For a
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
+ ```
72
83
 
73
84
  Do not publish with long-lived local PyPI tokens.
74
85
 
@@ -1,3 +1,3 @@
1
1
  """LightNow CLI - A command-line tool for managing MCP servers."""
2
2
 
3
- __version__ = "1.3.0" # 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,
@@ -602,6 +603,8 @@ def sync(
602
603
  )
603
604
  if local_proxy and remove_unmanaged_client_servers:
604
605
  removed_direct_servers = direct_server_aliases(existing, export_format)
606
+ if local_proxy and client == "codex" and export_format == "toml":
607
+ existing = remove_codex_mcp_server_alias(existing, connection)
605
608
  if (
606
609
  local_proxy
607
610
  and remove_unmanaged_client_servers
@@ -1205,6 +1208,11 @@ def build_local_proxy_config(
1205
1208
  "client_instance_id": client_instance_id,
1206
1209
  "device_hostname": socket.gethostname(),
1207
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),
1208
1216
  "telemetry_enabled": True,
1209
1217
  }
1210
1218
  if isinstance(local_proxy_settings, dict):
@@ -1296,6 +1304,13 @@ def register_runtime_device_client(
1296
1304
  "device": {
1297
1305
  "reported_hostname": socket.gethostname(),
1298
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,
1299
1314
  },
1300
1315
  "client": {
1301
1316
  "name": client,
@@ -1303,6 +1318,12 @@ def register_runtime_device_client(
1303
1318
  "profile": profile,
1304
1319
  "runner_name": "lightnow-local-proxy",
1305
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,
1306
1327
  "transport": transport,
1307
1328
  },
1308
1329
  },
@@ -2281,6 +2302,16 @@ def prepare_codex_local_proxy_config(existing: str) -> str:
2281
2302
  return strip_codex_mcp_server_tables(existing, preserved)
2282
2303
 
2283
2304
 
2305
+ def remove_codex_mcp_server_alias(existing: str, alias: str) -> str:
2306
+ """Remove one Codex MCP table before writing its managed replacement."""
2307
+ if not existing.strip():
2308
+ return existing
2309
+ entries = extract_mcp_entries(existing, "toml")
2310
+ if alias not in entries:
2311
+ return existing
2312
+ return strip_codex_mcp_server_tables(existing, set(entries) - {alias})
2313
+
2314
+
2284
2315
  def prepare_json_local_proxy_config(existing: str) -> str:
2285
2316
  """Remove direct JSON servers while preserving existing proxy connections."""
2286
2317
  if not existing.strip():
@@ -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: