nexo-brain 2.4.0 → 2.5.0

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 (80) hide show
  1. package/README.md +65 -2
  2. package/bin/nexo-brain.js +208 -11
  3. package/bin/nexo.js +55 -0
  4. package/community/skills/.gitkeep +1 -0
  5. package/package.json +5 -2
  6. package/src/auto_update.py +158 -8
  7. package/src/cli.py +605 -0
  8. package/src/cognitive/_ingest.py +1 -1
  9. package/src/cognitive/_memory.py +4 -4
  10. package/src/crons/manifest.json +8 -0
  11. package/src/dashboard/app.py +700 -35
  12. package/src/dashboard/templates/adaptive.html +112 -218
  13. package/src/dashboard/templates/artifacts.html +133 -0
  14. package/src/dashboard/templates/backups.html +136 -0
  15. package/src/dashboard/templates/base.html +413 -0
  16. package/src/dashboard/templates/calendar.html +523 -654
  17. package/src/dashboard/templates/chat.html +356 -0
  18. package/src/dashboard/templates/claims.html +259 -0
  19. package/src/dashboard/templates/cortex.html +262 -0
  20. package/src/dashboard/templates/credentials.html +128 -0
  21. package/src/dashboard/templates/crons.html +370 -0
  22. package/src/dashboard/templates/dashboard.html +383 -578
  23. package/src/dashboard/templates/dreams.html +252 -0
  24. package/src/dashboard/templates/email.html +160 -0
  25. package/src/dashboard/templates/evolution.html +189 -0
  26. package/src/dashboard/templates/feed.html +249 -0
  27. package/src/dashboard/templates/followup_health.html +170 -0
  28. package/src/dashboard/templates/graph.html +191 -269
  29. package/src/dashboard/templates/guard.html +259 -0
  30. package/src/dashboard/templates/inbox.html +220 -346
  31. package/src/dashboard/templates/memory.html +317 -197
  32. package/src/dashboard/templates/operations.html +521 -698
  33. package/src/dashboard/templates/plugins.html +185 -0
  34. package/src/dashboard/templates/rules.html +246 -0
  35. package/src/dashboard/templates/sentiment.html +247 -0
  36. package/src/dashboard/templates/sessions.html +215 -182
  37. package/src/dashboard/templates/skills.html +329 -0
  38. package/src/dashboard/templates/somatic.html +68 -172
  39. package/src/dashboard/templates/triggers.html +133 -0
  40. package/src/dashboard/templates/trust.html +360 -0
  41. package/src/db/__init__.py +5 -0
  42. package/src/db/_schema.py +16 -1
  43. package/src/db/_sessions.py +22 -0
  44. package/src/db/_skills.py +980 -274
  45. package/src/doctor/__init__.py +1 -0
  46. package/src/doctor/formatters.py +52 -0
  47. package/src/doctor/models.py +44 -0
  48. package/src/doctor/orchestrator.py +42 -0
  49. package/src/doctor/providers/__init__.py +1 -0
  50. package/src/doctor/providers/boot.py +206 -0
  51. package/src/doctor/providers/deep.py +292 -0
  52. package/src/doctor/providers/runtime.py +686 -0
  53. package/src/hooks/post-compact.sh +5 -1
  54. package/src/hooks/pre-compact.sh +1 -1
  55. package/src/plugins/doctor.py +36 -0
  56. package/src/plugins/evolution.py +2 -1
  57. package/src/plugins/skills.py +135 -175
  58. package/src/requirements.txt +1 -0
  59. package/src/script_registry.py +322 -0
  60. package/src/scripts/deep-sleep/apply_findings.py +63 -48
  61. package/src/scripts/deep-sleep/extract-prompt.md +14 -0
  62. package/src/scripts/deep-sleep/synthesize-prompt.md +36 -0
  63. package/src/scripts/deep-sleep/synthesize.py +37 -1
  64. package/src/scripts/nexo-dashboard.sh +29 -0
  65. package/src/scripts/nexo-day-orchestrator.sh +139 -0
  66. package/src/scripts/nexo-evolution-run.py +2 -1
  67. package/src/scripts/nexo-learning-housekeep.py +1 -1
  68. package/src/scripts/nexo-watchdog.sh +1 -1
  69. package/src/server.py +9 -5
  70. package/src/skills/run-runtime-doctor/guide.md +12 -0
  71. package/src/skills/run-runtime-doctor/script.py +21 -0
  72. package/src/skills/run-runtime-doctor/skill.json +25 -0
  73. package/src/skills_runtime.py +347 -0
  74. package/src/tools_menu.py +3 -2
  75. package/src/tools_sessions.py +126 -0
  76. package/src/user_context.py +46 -0
  77. package/templates/nexo_helper.py +45 -0
  78. package/templates/script-template.py +44 -0
  79. package/templates/skill-script-template.py +39 -0
  80. package/templates/skill-template.md +33 -0
@@ -94,6 +94,61 @@ def _read_package_version() -> str:
94
94
  return "unknown"
95
95
 
96
96
 
97
+ def _runtime_cli_wrapper_text() -> str:
98
+ return (
99
+ "#!/usr/bin/env bash\n"
100
+ "set -euo pipefail\n\n"
101
+ f'NEXO_HOME="{NEXO_HOME}"\n'
102
+ 'PYTHON="$NEXO_HOME/.venv/bin/python3"\n'
103
+ 'if [ ! -x "$PYTHON" ]; then\n'
104
+ ' if command -v python3 >/dev/null 2>&1; then\n'
105
+ ' PYTHON="python3"\n'
106
+ " else\n"
107
+ ' PYTHON="python"\n'
108
+ " fi\n"
109
+ "fi\n"
110
+ 'export NEXO_HOME\n'
111
+ 'export NEXO_CODE="$NEXO_HOME"\n'
112
+ 'exec "$PYTHON" "$NEXO_HOME/cli.py" "$@"\n'
113
+ )
114
+
115
+
116
+ def _shell_rc_files() -> list[Path]:
117
+ shell = os.environ.get("SHELL", "/bin/bash")
118
+ home_dir = Path.home()
119
+ if "zsh" in shell:
120
+ return [home_dir / ".zshrc"]
121
+ return [home_dir / ".bash_profile", home_dir / ".bashrc"]
122
+
123
+
124
+ def _ensure_runtime_cli_in_shell():
125
+ path_line = f'export PATH="{NEXO_HOME / "bin"}:$PATH"'
126
+ comment = "# NEXO runtime CLI"
127
+ for rc_file in _shell_rc_files():
128
+ try:
129
+ content = rc_file.read_text() if rc_file.exists() else ""
130
+ if path_line not in content:
131
+ with rc_file.open("a") as fh:
132
+ fh.write(f"\n{comment}\n{path_line}\n")
133
+ _log(f"Backfilled runtime CLI PATH in {rc_file.name}")
134
+ except Exception as e:
135
+ _log(f"Shell PATH backfill error for {rc_file.name}: {e}")
136
+
137
+
138
+ def _ensure_runtime_cli_wrapper():
139
+ try:
140
+ bin_dir = NEXO_HOME / "bin"
141
+ bin_dir.mkdir(parents=True, exist_ok=True)
142
+ wrapper = bin_dir / "nexo"
143
+ content = _runtime_cli_wrapper_text()
144
+ if not wrapper.exists() or wrapper.read_text() != content:
145
+ wrapper.write_text(content)
146
+ wrapper.chmod(0o755)
147
+ _log("Backfilled runtime CLI wrapper")
148
+ except Exception as e:
149
+ _log(f"Runtime CLI wrapper backfill error: {e}")
150
+
151
+
97
152
  # ── Hook sync ────────────────────────────────────────────────────────
98
153
 
99
154
  def _requirements_hash() -> str:
@@ -402,7 +457,12 @@ def _check_npm_version() -> str | None:
402
457
  if not latest:
403
458
  return None
404
459
  if latest != current and not current.endswith(latest):
405
- return f"NEXO update available: {current} -> {latest}. Run: npm update -g {pkg_name}"
460
+ try:
461
+ from user_context import get_context
462
+ _name = get_context().assistant_name
463
+ except Exception:
464
+ _name = "NEXO"
465
+ return f"{_name} update available: {current} -> {latest}. Run: npm update -g {pkg_name}"
406
466
  except Exception:
407
467
  pass
408
468
  return None
@@ -577,15 +637,12 @@ def _find_user_claude_md() -> Path | None:
577
637
 
578
638
  def _resolve_placeholders(template_text: str) -> str:
579
639
  """Fill {{NAME}} and {{NEXO_HOME}} from the user's existing CLAUDE.md or config."""
580
- # Try to read operator name from version.json
581
- name = "NEXO"
640
+ # Read operator name from calibration/version
582
641
  try:
583
- vf = NEXO_HOME / "version.json"
584
- if vf.exists():
585
- data = json.loads(vf.read_text())
586
- name = data.get("operator_name", name)
642
+ from user_context import get_context
643
+ name = get_context().assistant_name
587
644
  except Exception:
588
- pass
645
+ name = "NEXO"
589
646
 
590
647
  return (
591
648
  template_text
@@ -796,6 +853,99 @@ def auto_update_check() -> dict:
796
853
  except Exception as e:
797
854
  _log(f"scripts backfill error: {e}")
798
855
 
856
+ # Backfill runtime CLI modules for existing installs
857
+ try:
858
+ for fname in ("cli.py", "script_registry.py", "skills_runtime.py"):
859
+ src_file = SRC_DIR / fname
860
+ dest_file = NEXO_HOME / fname
861
+ if src_file.is_file() and (not dest_file.exists() or src_file.stat().st_mtime > dest_file.stat().st_mtime):
862
+ import shutil
863
+ shutil.copy2(str(src_file), str(dest_file))
864
+ _log(f"Backfilled {fname}")
865
+ except Exception as e:
866
+ _log(f"CLI backfill error: {e}")
867
+
868
+ _ensure_runtime_cli_wrapper()
869
+ _ensure_runtime_cli_in_shell()
870
+
871
+ # Backfill doctor package for existing installs
872
+ try:
873
+ doctor_src = SRC_DIR / "doctor"
874
+ doctor_dest = NEXO_HOME / "doctor"
875
+ if doctor_src.is_dir():
876
+ import shutil
877
+ if not doctor_dest.is_dir():
878
+ shutil.copytree(str(doctor_src), str(doctor_dest), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
879
+ _log("Backfilled doctor package")
880
+ else:
881
+ # Update existing files
882
+ for root, dirs, files in os.walk(str(doctor_src)):
883
+ dirs[:] = [d for d in dirs if d != "__pycache__"]
884
+ rel = os.path.relpath(root, str(doctor_src))
885
+ dest_dir = doctor_dest / rel
886
+ dest_dir.mkdir(parents=True, exist_ok=True)
887
+ for f in files:
888
+ if f.endswith(".pyc"):
889
+ continue
890
+ src_f = Path(root) / f
891
+ dst_f = dest_dir / f
892
+ if not dst_f.exists() or src_f.stat().st_mtime > dst_f.stat().st_mtime:
893
+ shutil.copy2(str(src_f), str(dst_f))
894
+ except Exception as e:
895
+ _log(f"Doctor backfill error: {e}")
896
+
897
+ # Backfill packaged core skills to a dedicated directory.
898
+ try:
899
+ skills_src = SRC_DIR / "skills"
900
+ skills_dest = NEXO_HOME / "skills-core"
901
+ if skills_src.is_dir():
902
+ import shutil
903
+ if not skills_dest.is_dir():
904
+ shutil.copytree(str(skills_src), str(skills_dest), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
905
+ _log("Backfilled skills-core")
906
+ else:
907
+ for root, dirs, files in os.walk(str(skills_src)):
908
+ dirs[:] = [d for d in dirs if d != "__pycache__"]
909
+ rel = os.path.relpath(root, str(skills_src))
910
+ dest_dir = skills_dest / rel
911
+ dest_dir.mkdir(parents=True, exist_ok=True)
912
+ for f in files:
913
+ if f.endswith(".pyc"):
914
+ continue
915
+ src_f = Path(root) / f
916
+ dst_f = dest_dir / f
917
+ if not dst_f.exists() or src_f.stat().st_mtime > dst_f.stat().st_mtime:
918
+ shutil.copy2(str(src_f), str(dst_f))
919
+ except Exception as e:
920
+ _log(f"Skills backfill error: {e}")
921
+
922
+ # Backfill MCP doctor plugin so existing installs expose nexo_doctor.
923
+ try:
924
+ plugin_src = SRC_DIR / "plugins" / "doctor.py"
925
+ plugin_dest = NEXO_HOME / "plugins" / "doctor.py"
926
+ plugin_dest.parent.mkdir(parents=True, exist_ok=True)
927
+ if plugin_src.is_file() and (not plugin_dest.exists() or plugin_src.stat().st_mtime > plugin_dest.stat().st_mtime):
928
+ import shutil
929
+ shutil.copy2(str(plugin_src), str(plugin_dest))
930
+ _log("Backfilled doctor plugin")
931
+ except Exception as e:
932
+ _log(f"Doctor plugin backfill error: {e}")
933
+
934
+ # Backfill script/skill templates for existing installs
935
+ try:
936
+ templates_src = REPO_DIR / "templates"
937
+ templates_dest = NEXO_HOME / "templates"
938
+ templates_dest.mkdir(parents=True, exist_ok=True)
939
+ for fname in ("script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"):
940
+ src_file = templates_src / fname
941
+ dest_file = templates_dest / fname
942
+ if src_file.is_file() and (not dest_file.exists() or src_file.stat().st_mtime > dest_file.stat().st_mtime):
943
+ import shutil
944
+ shutil.copy2(str(src_file), str(dest_file))
945
+ _log(f"Backfilled template {fname}")
946
+ except Exception as e:
947
+ _log(f"Template backfill error: {e}")
948
+
799
949
  # CLAUDE.md version migration
800
950
  try:
801
951
  result["claude_md_update"] = _migrate_claude_md()