nexo-brain 5.3.3 → 5.3.5

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
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.3.3",
3
+ "version": "5.3.5",
4
4
  "description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
5
5
  "author": {
6
6
  "name": "NEXO Brain",
package/README.md CHANGED
@@ -18,6 +18,8 @@
18
18
 
19
19
  [Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
20
20
 
21
+ Version `5.3.4` closes the last packaged-runtime core/personal leak from the hook migration work: legacy hook aliases stay out of the personal bucket, `nexo update` removes those retired aliases when the canonical hooks already exist, and both `nexo` and `nexo chat` now show latest vs installed version at a glance.
22
+
21
23
  Start here:
22
24
  - [5-minute quickstart](docs/quickstart-5-minutes.md)
23
25
  - [Workflow quickstart](docs/workflows-quickstart.md)
@@ -87,7 +89,7 @@ Versions `3.1.7` through `3.2.0` close the recent-memory gap:
87
89
  - when even that misses, NEXO now exposes raw transcript fallback tools for Claude Code and Codex session stores
88
90
  - NEXO can now inspect itself through a live system catalog derived from canonical sources instead of relying only on stale docs or operator memory
89
91
 
90
- Version `5.3.3` closes the remaining packaged-runtime doctor mismatch: the built-in hourly backup helper is now inventoried as a core LaunchAgent, so clean installs no longer get a false unknown-LaunchAgent warning. Version `5.3.2` already hardened the runtime boundary by persisting which runtime scripts/hooks are core product artifacts, keeping `nexo scripts` from mixing those into the personal bucket, and migrating the legacy Claude Code heartbeat wrappers into managed core hooks.
92
+ Version `5.3.5` keeps CLI version visibility honest right after `nexo update`: if the cached npm version lags behind the runtime you just installed, `nexo` / `nexo chat` now clamp `Latest` to the installed version and refresh the cache instead of showing a stale older release. Version `5.3.4` already cleaned up legacy core alias leakage and added the version-status banner. Version `5.3.3` closed the remaining packaged-runtime doctor mismatch: the built-in hourly backup helper is now inventoried as a core LaunchAgent, so clean installs no longer get a false unknown-LaunchAgent warning. Version `5.3.2` already hardened the runtime boundary by persisting which runtime scripts/hooks are core product artifacts, keeping `nexo scripts` from mixing those into the personal bucket, and migrating the legacy Claude Code heartbeat wrappers into managed core hooks.
91
93
 
92
94
  Version `5.3.1` normalizes packaged npm installs so they behave like packaged npm installs: `nexo update` now keeps the runtime anchored to `~/.nexo`, refreshes packaged bootstrap/client artifacts after upgrade, avoids repo-only release-artifact drift in installed runtimes, and keeps personal scripts on the canonical packaged path.
93
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.3.3",
3
+ "version": "5.3.5",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
6
6
  "homepage": "https://nexo-brain.com",
@@ -364,6 +364,12 @@ def _cleanup_retired_runtime_files():
364
364
  NEXO_HOME / "scripts" / "heartbeat-user-msg.sh",
365
365
  NEXO_HOME / "hooks" / "heartbeat-guard.sh",
366
366
  ]
367
+ conditional_retired = [
368
+ (NEXO_HOME / "scripts" / "nexo-postcompact.sh", NEXO_HOME / "hooks" / "post-compact.sh"),
369
+ (NEXO_HOME / "scripts" / "nexo-memory-precompact.sh", NEXO_HOME / "hooks" / "pre-compact.sh"),
370
+ (NEXO_HOME / "scripts" / "nexo-memory-stop.sh", NEXO_HOME / "hooks" / "session-stop.sh"),
371
+ (NEXO_HOME / "scripts" / "nexo-session-briefing.sh", NEXO_HOME / "hooks" / "session-start.sh"),
372
+ ]
367
373
  for target in retired:
368
374
  try:
369
375
  if target.exists():
@@ -375,6 +381,13 @@ def _cleanup_retired_runtime_files():
375
381
  _log(f"Removed retired runtime file: {target.name}")
376
382
  except Exception as e:
377
383
  _log(f"Retired runtime cleanup warning ({target.name}): {e}")
384
+ for target, canonical in conditional_retired:
385
+ try:
386
+ if target.exists() and canonical.exists():
387
+ target.unlink()
388
+ _log(f"Removed retired runtime alias: {target.name} (canonical: {canonical.name})")
389
+ except Exception as e:
390
+ _log(f"Retired runtime alias cleanup warning ({target.name}): {e}")
378
391
 
379
392
 
380
393
  def _sync_crons():
package/src/cli.py CHANGED
@@ -38,6 +38,7 @@ import os
38
38
  import shutil
39
39
  import subprocess
40
40
  import sys
41
+ import time
41
42
  from pathlib import Path
42
43
 
43
44
  from runtime_home import export_resolved_nexo_home
@@ -49,6 +50,8 @@ TERMINAL_CLIENT_LABELS = {
49
50
  "codex": "Codex",
50
51
  }
51
52
  TERMINAL_CLIENT_ORDER = ("claude_code", "codex")
53
+ VERSION_STATUS_CACHE = NEXO_HOME / "config" / "cli-version-status.json"
54
+ LATEST_NPM_PACKAGE = "nexo-brain"
52
55
 
53
56
 
54
57
  def _get_version() -> str:
@@ -67,6 +70,87 @@ def _get_version() -> str:
67
70
  continue
68
71
  return "?"
69
72
 
73
+
74
+ def _load_latest_version_cache(max_age_seconds: int = 6 * 3600) -> str | None:
75
+ try:
76
+ payload = json.loads(VERSION_STATUS_CACHE.read_text())
77
+ except Exception:
78
+ return None
79
+ version = str(payload.get("latest", "")).strip()
80
+ checked_at = float(payload.get("checked_at", 0) or 0)
81
+ if not version:
82
+ return None
83
+ if checked_at and (time.time() - checked_at) > max_age_seconds:
84
+ return None
85
+ return version
86
+
87
+
88
+ def _save_latest_version_cache(version: str) -> None:
89
+ VERSION_STATUS_CACHE.parent.mkdir(parents=True, exist_ok=True)
90
+ VERSION_STATUS_CACHE.write_text(json.dumps({
91
+ "latest": version,
92
+ "checked_at": time.time(),
93
+ }))
94
+
95
+
96
+ def _fetch_latest_version(timeout_seconds: int = 2) -> str | None:
97
+ try:
98
+ result = subprocess.run(
99
+ ["npm", "view", LATEST_NPM_PACKAGE, "version"],
100
+ capture_output=True,
101
+ text=True,
102
+ timeout=timeout_seconds,
103
+ )
104
+ except Exception:
105
+ return None
106
+ if result.returncode != 0:
107
+ return None
108
+ latest = result.stdout.strip()
109
+ if not latest:
110
+ return None
111
+ try:
112
+ _save_latest_version_cache(latest)
113
+ except Exception:
114
+ pass
115
+ return latest
116
+
117
+
118
+ def _should_refresh_latest_version() -> bool:
119
+ try:
120
+ return sys.stdout.isatty() or sys.stderr.isatty()
121
+ except Exception:
122
+ return False
123
+
124
+
125
+ def _version_sort_key(raw: str) -> tuple[tuple[int, ...], int, str]:
126
+ value = str(raw or "").strip()
127
+ base, _, suffix = value.partition("-")
128
+ parts: list[int] = []
129
+ for piece in base.split("."):
130
+ try:
131
+ parts.append(int(piece))
132
+ except Exception:
133
+ parts.append(-1)
134
+ while len(parts) < 3:
135
+ parts.append(0)
136
+ return (tuple(parts), 1 if not suffix else 0, suffix)
137
+
138
+
139
+ def _version_status_line() -> str:
140
+ installed = _get_version()
141
+ latest = _load_latest_version_cache()
142
+ if latest is None and _should_refresh_latest_version():
143
+ latest = _fetch_latest_version()
144
+ if latest and installed and _version_sort_key(latest) < _version_sort_key(installed):
145
+ latest = installed
146
+ try:
147
+ _save_latest_version_cache(installed)
148
+ except Exception:
149
+ pass
150
+ if latest:
151
+ return f"NEXO Latest: v{latest} | Installed: v{installed}"
152
+ return f"NEXO Installed: v{installed}"
153
+
70
154
  # Ensure src/ is on path for imports
71
155
  if str(NEXO_CODE) not in sys.path:
72
156
  sys.path.insert(0, str(NEXO_CODE))
@@ -1011,6 +1095,7 @@ def _prompt_for_terminal_client(
1011
1095
  def _chat(args):
1012
1096
  target = args.path or "."
1013
1097
  selected_client = getattr(args, "client", None)
1098
+ print(f"[NEXO] {_version_status_line()}", file=sys.stderr)
1014
1099
 
1015
1100
  try:
1016
1101
  from auto_update import startup_preflight
@@ -1499,6 +1584,7 @@ def _uninstall(args):
1499
1584
  def _print_help():
1500
1585
  v = _get_version()
1501
1586
  print(f"""NEXO Runtime CLI v{v}
1587
+ {_version_status_line()}
1502
1588
 
1503
1589
  Commands:
1504
1590
  nexo chat [path] [--client claude_code|codex] Launch a NEXO terminal client
@@ -50,6 +50,10 @@ _LEGACY_CORE_RUNTIME_FILES = {
50
50
  "heartbeat-enforcement.py",
51
51
  "heartbeat-posttool.sh",
52
52
  "heartbeat-user-msg.sh",
53
+ "nexo-memory-precompact.sh",
54
+ "nexo-memory-stop.sh",
55
+ "nexo-postcompact.sh",
56
+ "nexo-session-briefing.sh",
53
57
  }
54
58
 
55
59
  # Forbidden patterns — direct DB access from personal scripts