nexo-brain 2.4.0 → 2.5.1
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.
- package/README.md +80 -4
- package/bin/nexo-brain.js +238 -12
- package/bin/nexo.js +55 -0
- package/community/skills/.gitkeep +1 -0
- package/package.json +11 -3
- package/src/auto_update.py +193 -9
- package/src/cli.py +719 -0
- package/src/cognitive/_ingest.py +1 -1
- package/src/cognitive/_memory.py +4 -4
- package/src/crons/manifest.json +8 -0
- package/src/dashboard/app.py +700 -35
- package/src/dashboard/templates/adaptive.html +112 -218
- package/src/dashboard/templates/artifacts.html +133 -0
- package/src/dashboard/templates/backups.html +136 -0
- package/src/dashboard/templates/base.html +413 -0
- package/src/dashboard/templates/calendar.html +523 -654
- package/src/dashboard/templates/chat.html +356 -0
- package/src/dashboard/templates/claims.html +259 -0
- package/src/dashboard/templates/cortex.html +262 -0
- package/src/dashboard/templates/credentials.html +128 -0
- package/src/dashboard/templates/crons.html +370 -0
- package/src/dashboard/templates/dashboard.html +383 -578
- package/src/dashboard/templates/dreams.html +252 -0
- package/src/dashboard/templates/email.html +160 -0
- package/src/dashboard/templates/evolution.html +189 -0
- package/src/dashboard/templates/feed.html +249 -0
- package/src/dashboard/templates/followup_health.html +170 -0
- package/src/dashboard/templates/graph.html +191 -269
- package/src/dashboard/templates/guard.html +259 -0
- package/src/dashboard/templates/inbox.html +220 -346
- package/src/dashboard/templates/memory.html +317 -197
- package/src/dashboard/templates/operations.html +521 -698
- package/src/dashboard/templates/plugins.html +185 -0
- package/src/dashboard/templates/rules.html +246 -0
- package/src/dashboard/templates/sentiment.html +247 -0
- package/src/dashboard/templates/sessions.html +215 -182
- package/src/dashboard/templates/skills.html +329 -0
- package/src/dashboard/templates/somatic.html +68 -172
- package/src/dashboard/templates/triggers.html +133 -0
- package/src/dashboard/templates/trust.html +360 -0
- package/src/db/__init__.py +5 -0
- package/src/db/_schema.py +16 -1
- package/src/db/_sessions.py +22 -0
- package/src/db/_skills.py +980 -274
- package/src/doctor/__init__.py +1 -0
- package/src/doctor/formatters.py +52 -0
- package/src/doctor/models.py +44 -0
- package/src/doctor/orchestrator.py +42 -0
- package/src/doctor/providers/__init__.py +1 -0
- package/src/doctor/providers/boot.py +206 -0
- package/src/doctor/providers/deep.py +292 -0
- package/src/doctor/providers/runtime.py +686 -0
- package/src/evolution_cycle.py +86 -6
- package/src/hooks/post-compact.sh +5 -1
- package/src/hooks/pre-compact.sh +1 -1
- package/src/plugins/doctor.py +36 -0
- package/src/plugins/evolution.py +11 -3
- package/src/plugins/skills.py +135 -175
- package/src/requirements.txt +1 -0
- package/src/script_registry.py +322 -0
- package/src/scripts/deep-sleep/apply_findings.py +63 -48
- package/src/scripts/deep-sleep/extract-prompt.md +14 -0
- package/src/scripts/deep-sleep/synthesize-prompt.md +36 -0
- package/src/scripts/deep-sleep/synthesize.py +37 -1
- package/src/scripts/nexo-dashboard.sh +29 -0
- package/src/scripts/nexo-day-orchestrator.sh +139 -0
- package/src/scripts/nexo-evolution-run.py +141 -54
- package/src/scripts/nexo-learning-housekeep.py +1 -1
- package/src/scripts/nexo-watchdog.sh +1 -1
- package/src/server.py +9 -5
- package/src/skills/run-runtime-doctor/guide.md +12 -0
- package/src/skills/run-runtime-doctor/script.py +21 -0
- package/src/skills/run-runtime-doctor/skill.json +25 -0
- package/src/skills_runtime.py +347 -0
- package/src/tools_menu.py +3 -2
- package/src/tools_sessions.py +126 -0
- package/src/user_context.py +46 -0
- package/templates/nexo_helper.py +45 -0
- package/templates/script-template.py +44 -0
- package/templates/skill-script-template.py +39 -0
- package/templates/skill-template.md +33 -0
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO — Cognitive co-operator for Claude Code. Memory, emotional intelligence, overnight learning (Deep Sleep), cron management, trust scoring, and adaptive calibration.",
|
|
6
6
|
"bin": {
|
|
7
|
-
"nexo-brain": "./bin/nexo-brain.js"
|
|
7
|
+
"nexo-brain": "./bin/nexo-brain.js",
|
|
8
|
+
"nexo": "./bin/nexo.js"
|
|
8
9
|
},
|
|
9
10
|
"keywords": [
|
|
10
11
|
"claude-code",
|
|
@@ -38,7 +39,12 @@
|
|
|
38
39
|
"cron-manifest",
|
|
39
40
|
"session-tone",
|
|
40
41
|
"mood-tracking",
|
|
41
|
-
"productivity-analysis"
|
|
42
|
+
"productivity-analysis",
|
|
43
|
+
"runtime-cli",
|
|
44
|
+
"doctor",
|
|
45
|
+
"executable-skills",
|
|
46
|
+
"day-orchestrator",
|
|
47
|
+
"work-continuity"
|
|
42
48
|
],
|
|
43
49
|
"author": "NEXO Brain <info@nexo-brain.com>",
|
|
44
50
|
"license": "AGPL-3.0",
|
|
@@ -54,8 +60,10 @@
|
|
|
54
60
|
},
|
|
55
61
|
"files": [
|
|
56
62
|
"bin/nexo-brain.js",
|
|
63
|
+
"bin/nexo.js",
|
|
57
64
|
"bin/postinstall.js",
|
|
58
65
|
"src/",
|
|
66
|
+
"community/",
|
|
59
67
|
"!src/**/__pycache__",
|
|
60
68
|
"!src/**/*.pyc",
|
|
61
69
|
"!src/**/*.pyo",
|
package/src/auto_update.py
CHANGED
|
@@ -8,6 +8,7 @@ This is separate from plugins/update.py which handles MANUAL updates with rollba
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import hashlib
|
|
11
12
|
import os
|
|
12
13
|
import re
|
|
13
14
|
import subprocess
|
|
@@ -56,6 +57,30 @@ def _write_last_check(data: dict):
|
|
|
56
57
|
_log(f"Failed to write last-check file: {e}")
|
|
57
58
|
|
|
58
59
|
|
|
60
|
+
def _sync_watchdog_hash_registry():
|
|
61
|
+
"""Keep the immutable-hash registry aligned with the installed watchdog script."""
|
|
62
|
+
try:
|
|
63
|
+
watchdog_file = NEXO_HOME / "scripts" / "nexo-watchdog.sh"
|
|
64
|
+
if not watchdog_file.exists():
|
|
65
|
+
return
|
|
66
|
+
registry_file = NEXO_HOME / "scripts" / ".watchdog-hashes"
|
|
67
|
+
entries: dict[str, str] = {}
|
|
68
|
+
if registry_file.exists():
|
|
69
|
+
for line in registry_file.read_text().splitlines():
|
|
70
|
+
if "|" not in line:
|
|
71
|
+
continue
|
|
72
|
+
filepath, expected = line.split("|", 1)
|
|
73
|
+
if filepath:
|
|
74
|
+
entries[filepath] = expected
|
|
75
|
+
actual_hash = hashlib.sha256(watchdog_file.read_bytes()).hexdigest()
|
|
76
|
+
entries[str(watchdog_file)] = actual_hash
|
|
77
|
+
registry_file.write_text(
|
|
78
|
+
"\n".join(f"{filepath}|{digest}" for filepath, digest in sorted(entries.items())) + "\n"
|
|
79
|
+
)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
_log(f"watchdog hash registry sync error: {e}")
|
|
82
|
+
|
|
83
|
+
|
|
59
84
|
def _is_git_repo() -> bool:
|
|
60
85
|
"""Check if REPO_DIR is inside a git repository."""
|
|
61
86
|
try:
|
|
@@ -94,6 +119,61 @@ def _read_package_version() -> str:
|
|
|
94
119
|
return "unknown"
|
|
95
120
|
|
|
96
121
|
|
|
122
|
+
def _runtime_cli_wrapper_text() -> str:
|
|
123
|
+
return (
|
|
124
|
+
"#!/usr/bin/env bash\n"
|
|
125
|
+
"set -euo pipefail\n\n"
|
|
126
|
+
f'NEXO_HOME="{NEXO_HOME}"\n'
|
|
127
|
+
'PYTHON="$NEXO_HOME/.venv/bin/python3"\n'
|
|
128
|
+
'if [ ! -x "$PYTHON" ]; then\n'
|
|
129
|
+
' if command -v python3 >/dev/null 2>&1; then\n'
|
|
130
|
+
' PYTHON="python3"\n'
|
|
131
|
+
" else\n"
|
|
132
|
+
' PYTHON="python"\n'
|
|
133
|
+
" fi\n"
|
|
134
|
+
"fi\n"
|
|
135
|
+
'export NEXO_HOME\n'
|
|
136
|
+
'export NEXO_CODE="$NEXO_HOME"\n'
|
|
137
|
+
'exec "$PYTHON" "$NEXO_HOME/cli.py" "$@"\n'
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _shell_rc_files() -> list[Path]:
|
|
142
|
+
shell = os.environ.get("SHELL", "/bin/bash")
|
|
143
|
+
home_dir = Path.home()
|
|
144
|
+
if "zsh" in shell:
|
|
145
|
+
return [home_dir / ".zshrc"]
|
|
146
|
+
return [home_dir / ".bash_profile", home_dir / ".bashrc"]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _ensure_runtime_cli_in_shell():
|
|
150
|
+
path_line = f'export PATH="{NEXO_HOME / "bin"}:$PATH"'
|
|
151
|
+
comment = "# NEXO runtime CLI"
|
|
152
|
+
for rc_file in _shell_rc_files():
|
|
153
|
+
try:
|
|
154
|
+
content = rc_file.read_text() if rc_file.exists() else ""
|
|
155
|
+
if path_line not in content:
|
|
156
|
+
with rc_file.open("a") as fh:
|
|
157
|
+
fh.write(f"\n{comment}\n{path_line}\n")
|
|
158
|
+
_log(f"Backfilled runtime CLI PATH in {rc_file.name}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
_log(f"Shell PATH backfill error for {rc_file.name}: {e}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _ensure_runtime_cli_wrapper():
|
|
164
|
+
try:
|
|
165
|
+
bin_dir = NEXO_HOME / "bin"
|
|
166
|
+
bin_dir.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
wrapper = bin_dir / "nexo"
|
|
168
|
+
content = _runtime_cli_wrapper_text()
|
|
169
|
+
if not wrapper.exists() or wrapper.read_text() != content:
|
|
170
|
+
wrapper.write_text(content)
|
|
171
|
+
wrapper.chmod(0o755)
|
|
172
|
+
_log("Backfilled runtime CLI wrapper")
|
|
173
|
+
except Exception as e:
|
|
174
|
+
_log(f"Runtime CLI wrapper backfill error: {e}")
|
|
175
|
+
|
|
176
|
+
|
|
97
177
|
# ── Hook sync ────────────────────────────────────────────────────────
|
|
98
178
|
|
|
99
179
|
def _requirements_hash() -> str:
|
|
@@ -402,7 +482,12 @@ def _check_npm_version() -> str | None:
|
|
|
402
482
|
if not latest:
|
|
403
483
|
return None
|
|
404
484
|
if latest != current and not current.endswith(latest):
|
|
405
|
-
|
|
485
|
+
try:
|
|
486
|
+
from user_context import get_context
|
|
487
|
+
_name = get_context().assistant_name
|
|
488
|
+
except Exception:
|
|
489
|
+
_name = "NEXO"
|
|
490
|
+
return f"{_name} update available: {current} -> {latest}. Run: npm update -g {pkg_name}"
|
|
406
491
|
except Exception:
|
|
407
492
|
pass
|
|
408
493
|
return None
|
|
@@ -577,15 +662,12 @@ def _find_user_claude_md() -> Path | None:
|
|
|
577
662
|
|
|
578
663
|
def _resolve_placeholders(template_text: str) -> str:
|
|
579
664
|
"""Fill {{NAME}} and {{NEXO_HOME}} from the user's existing CLAUDE.md or config."""
|
|
580
|
-
#
|
|
581
|
-
name = "NEXO"
|
|
665
|
+
# Read operator name from calibration/version
|
|
582
666
|
try:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
data = json.loads(vf.read_text())
|
|
586
|
-
name = data.get("operator_name", name)
|
|
667
|
+
from user_context import get_context
|
|
668
|
+
name = get_context().assistant_name
|
|
587
669
|
except Exception:
|
|
588
|
-
|
|
670
|
+
name = "NEXO"
|
|
589
671
|
|
|
590
672
|
return (
|
|
591
673
|
template_text
|
|
@@ -754,13 +836,14 @@ def auto_update_check() -> dict:
|
|
|
754
836
|
# Backfill evolution-objective.json for existing installs
|
|
755
837
|
try:
|
|
756
838
|
evo_obj_path = NEXO_HOME / "brain" / "evolution-objective.json"
|
|
839
|
+
from evolution_cycle import normalize_objective
|
|
757
840
|
if not evo_obj_path.exists():
|
|
758
841
|
(NEXO_HOME / "brain").mkdir(parents=True, exist_ok=True)
|
|
759
842
|
default_objective = {
|
|
760
843
|
"objective": "Improve operational excellence and reduce repeated errors",
|
|
761
844
|
"focus_areas": ["error_prevention", "proactivity", "memory_quality"],
|
|
762
845
|
"evolution_enabled": True,
|
|
763
|
-
"evolution_mode": "
|
|
846
|
+
"evolution_mode": "auto",
|
|
764
847
|
"dimensions": {
|
|
765
848
|
"episodic_memory": {"current": 0, "target": 90},
|
|
766
849
|
"autonomy": {"current": 0, "target": 80},
|
|
@@ -774,6 +857,12 @@ def auto_update_check() -> dict:
|
|
|
774
857
|
}
|
|
775
858
|
evo_obj_path.write_text(json.dumps(default_objective, indent=2))
|
|
776
859
|
_log("Backfilled evolution-objective.json for existing install")
|
|
860
|
+
else:
|
|
861
|
+
raw_objective = json.loads(evo_obj_path.read_text())
|
|
862
|
+
normalized = normalize_objective(raw_objective)
|
|
863
|
+
if normalized != raw_objective:
|
|
864
|
+
evo_obj_path.write_text(json.dumps(normalized, indent=2, ensure_ascii=False))
|
|
865
|
+
_log("Normalized legacy evolution-objective.json")
|
|
777
866
|
except Exception as e:
|
|
778
867
|
_log(f"evolution-objective.json backfill error: {e}")
|
|
779
868
|
|
|
@@ -796,6 +885,101 @@ def auto_update_check() -> dict:
|
|
|
796
885
|
except Exception as e:
|
|
797
886
|
_log(f"scripts backfill error: {e}")
|
|
798
887
|
|
|
888
|
+
_sync_watchdog_hash_registry()
|
|
889
|
+
|
|
890
|
+
# Backfill runtime CLI modules for existing installs
|
|
891
|
+
try:
|
|
892
|
+
for fname in ("cli.py", "script_registry.py", "skills_runtime.py"):
|
|
893
|
+
src_file = SRC_DIR / fname
|
|
894
|
+
dest_file = NEXO_HOME / fname
|
|
895
|
+
if src_file.is_file() and (not dest_file.exists() or src_file.stat().st_mtime > dest_file.stat().st_mtime):
|
|
896
|
+
import shutil
|
|
897
|
+
shutil.copy2(str(src_file), str(dest_file))
|
|
898
|
+
_log(f"Backfilled {fname}")
|
|
899
|
+
except Exception as e:
|
|
900
|
+
_log(f"CLI backfill error: {e}")
|
|
901
|
+
|
|
902
|
+
_ensure_runtime_cli_wrapper()
|
|
903
|
+
_ensure_runtime_cli_in_shell()
|
|
904
|
+
|
|
905
|
+
# Backfill doctor package for existing installs
|
|
906
|
+
try:
|
|
907
|
+
doctor_src = SRC_DIR / "doctor"
|
|
908
|
+
doctor_dest = NEXO_HOME / "doctor"
|
|
909
|
+
if doctor_src.is_dir():
|
|
910
|
+
import shutil
|
|
911
|
+
if not doctor_dest.is_dir():
|
|
912
|
+
shutil.copytree(str(doctor_src), str(doctor_dest), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
|
|
913
|
+
_log("Backfilled doctor package")
|
|
914
|
+
else:
|
|
915
|
+
# Update existing files
|
|
916
|
+
for root, dirs, files in os.walk(str(doctor_src)):
|
|
917
|
+
dirs[:] = [d for d in dirs if d != "__pycache__"]
|
|
918
|
+
rel = os.path.relpath(root, str(doctor_src))
|
|
919
|
+
dest_dir = doctor_dest / rel
|
|
920
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
921
|
+
for f in files:
|
|
922
|
+
if f.endswith(".pyc"):
|
|
923
|
+
continue
|
|
924
|
+
src_f = Path(root) / f
|
|
925
|
+
dst_f = dest_dir / f
|
|
926
|
+
if not dst_f.exists() or src_f.stat().st_mtime > dst_f.stat().st_mtime:
|
|
927
|
+
shutil.copy2(str(src_f), str(dst_f))
|
|
928
|
+
except Exception as e:
|
|
929
|
+
_log(f"Doctor backfill error: {e}")
|
|
930
|
+
|
|
931
|
+
# Backfill packaged core skills to a dedicated directory.
|
|
932
|
+
try:
|
|
933
|
+
skills_src = SRC_DIR / "skills"
|
|
934
|
+
skills_dest = NEXO_HOME / "skills-core"
|
|
935
|
+
if skills_src.is_dir():
|
|
936
|
+
import shutil
|
|
937
|
+
if not skills_dest.is_dir():
|
|
938
|
+
shutil.copytree(str(skills_src), str(skills_dest), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
|
|
939
|
+
_log("Backfilled skills-core")
|
|
940
|
+
else:
|
|
941
|
+
for root, dirs, files in os.walk(str(skills_src)):
|
|
942
|
+
dirs[:] = [d for d in dirs if d != "__pycache__"]
|
|
943
|
+
rel = os.path.relpath(root, str(skills_src))
|
|
944
|
+
dest_dir = skills_dest / rel
|
|
945
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
946
|
+
for f in files:
|
|
947
|
+
if f.endswith(".pyc"):
|
|
948
|
+
continue
|
|
949
|
+
src_f = Path(root) / f
|
|
950
|
+
dst_f = dest_dir / f
|
|
951
|
+
if not dst_f.exists() or src_f.stat().st_mtime > dst_f.stat().st_mtime:
|
|
952
|
+
shutil.copy2(str(src_f), str(dst_f))
|
|
953
|
+
except Exception as e:
|
|
954
|
+
_log(f"Skills backfill error: {e}")
|
|
955
|
+
|
|
956
|
+
# Backfill MCP doctor plugin so existing installs expose nexo_doctor.
|
|
957
|
+
try:
|
|
958
|
+
plugin_src = SRC_DIR / "plugins" / "doctor.py"
|
|
959
|
+
plugin_dest = NEXO_HOME / "plugins" / "doctor.py"
|
|
960
|
+
plugin_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
961
|
+
if plugin_src.is_file() and (not plugin_dest.exists() or plugin_src.stat().st_mtime > plugin_dest.stat().st_mtime):
|
|
962
|
+
import shutil
|
|
963
|
+
shutil.copy2(str(plugin_src), str(plugin_dest))
|
|
964
|
+
_log("Backfilled doctor plugin")
|
|
965
|
+
except Exception as e:
|
|
966
|
+
_log(f"Doctor plugin backfill error: {e}")
|
|
967
|
+
|
|
968
|
+
# Backfill script/skill templates for existing installs
|
|
969
|
+
try:
|
|
970
|
+
templates_src = REPO_DIR / "templates"
|
|
971
|
+
templates_dest = NEXO_HOME / "templates"
|
|
972
|
+
templates_dest.mkdir(parents=True, exist_ok=True)
|
|
973
|
+
for fname in ("script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"):
|
|
974
|
+
src_file = templates_src / fname
|
|
975
|
+
dest_file = templates_dest / fname
|
|
976
|
+
if src_file.is_file() and (not dest_file.exists() or src_file.stat().st_mtime > dest_file.stat().st_mtime):
|
|
977
|
+
import shutil
|
|
978
|
+
shutil.copy2(str(src_file), str(dest_file))
|
|
979
|
+
_log(f"Backfilled template {fname}")
|
|
980
|
+
except Exception as e:
|
|
981
|
+
_log(f"Template backfill error: {e}")
|
|
982
|
+
|
|
799
983
|
# CLAUDE.md version migration
|
|
800
984
|
try:
|
|
801
985
|
result["claude_md_update"] = _migrate_claude_md()
|