nexo-brain 1.7.0 → 2.1.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 (261) hide show
  1. package/README.md +160 -60
  2. package/bin/nexo-brain.js +680 -381
  3. package/package.json +18 -3
  4. package/scripts/migrate-to-unified.sh +813 -0
  5. package/scripts/migrate-v1.7-to-v1.8.py +214 -0
  6. package/scripts/pre-commit-check.sh +1 -1
  7. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  8. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  9. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  10. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  11. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  12. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  13. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  14. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  15. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  16. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  17. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  18. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  19. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  20. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  21. package/src/auto_close_sessions.py +1 -1
  22. package/src/auto_update.py +634 -0
  23. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  24. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  25. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  26. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  27. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  28. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  29. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  30. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  31. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  32. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  33. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  34. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  35. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  36. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  37. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  38. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  39. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  40. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  41. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  42. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  43. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  44. package/src/cognitive/_core.py +7 -3
  45. package/src/cognitive/_decay.py +1 -1
  46. package/src/cognitive/_search.py +1 -0
  47. package/src/cognitive/_trust.py +3 -3
  48. package/src/crons/manifest.json +106 -0
  49. package/src/crons/sync.py +217 -0
  50. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  51. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  52. package/src/dashboard/app.py +24 -4
  53. package/src/dashboard/templates/dashboard.html +3 -2
  54. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  55. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  56. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  57. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  58. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  59. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  60. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  61. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  62. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  63. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  64. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  65. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  66. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  67. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  68. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  69. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  70. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  71. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  72. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  73. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  74. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  75. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  76. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  77. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  78. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  79. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  80. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  81. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  82. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  83. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  84. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  85. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  86. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  87. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  88. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  89. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  90. package/src/db/_core.py +5 -1
  91. package/src/db/_episodic.py +2 -4
  92. package/src/db/_reminders.py +45 -6
  93. package/src/db/_schema.py +31 -0
  94. package/src/evolution_cycle.py +33 -11
  95. package/src/hooks/auto_capture.py +1 -1
  96. package/src/hooks/capture-tool-logs.sh +76 -0
  97. package/src/hooks/inbox-hook.sh +2 -1
  98. package/src/hooks/post-compact.sh +2 -1
  99. package/src/hooks/pre-compact.sh +104 -2
  100. package/src/hooks/session-start.sh +6 -2
  101. package/src/hooks/session-stop.sh +4 -2
  102. package/src/kg_populate.py +4 -1
  103. package/src/migrate_embeddings.py +4 -1
  104. package/src/plugin_loader.py +100 -34
  105. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  106. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  107. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  108. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  109. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  110. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  111. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  112. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  113. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  114. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  115. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  116. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  117. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  118. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  119. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  120. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  121. package/src/plugins/agents.py +2 -2
  122. package/src/plugins/backup.py +5 -4
  123. package/src/plugins/core_rules.py +37 -16
  124. package/src/plugins/episodic_memory.py +14 -5
  125. package/src/plugins/evolution.py +6 -2
  126. package/src/plugins/guard.py +20 -11
  127. package/src/plugins/update.py +256 -0
  128. package/src/requirements.txt +12 -0
  129. package/src/scripts/check-context.py +8 -3
  130. package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
  131. package/src/scripts/deep-sleep/apply_findings.py +514 -169
  132. package/src/scripts/deep-sleep/collect.py +480 -0
  133. package/src/scripts/deep-sleep/extract-prompt.md +233 -0
  134. package/src/scripts/deep-sleep/extract.py +249 -0
  135. package/src/scripts/deep-sleep/synthesize-prompt.md +168 -0
  136. package/src/scripts/deep-sleep/synthesize.py +191 -0
  137. package/src/scripts/nexo-auto-update.py +4 -211
  138. package/src/scripts/nexo-backup.sh +5 -13
  139. package/src/scripts/nexo-brain-activation.sh +26 -26
  140. package/src/scripts/nexo-catchup.py +36 -25
  141. package/src/scripts/nexo-cognitive-decay.py +7 -3
  142. package/src/scripts/nexo-daily-self-audit.py +44 -15
  143. package/src/scripts/nexo-deep-sleep.sh +31 -16
  144. package/src/scripts/nexo-evolution-run.py +21 -11
  145. package/src/scripts/nexo-followup-hygiene.py +6 -4
  146. package/src/scripts/nexo-github-monitor.py +12 -8
  147. package/src/scripts/nexo-immune.py +6 -4
  148. package/src/scripts/nexo-inbox-hook.sh +2 -1
  149. package/src/scripts/nexo-install.py +4 -225
  150. package/src/scripts/nexo-learning-housekeep.py +7 -3
  151. package/src/scripts/nexo-learning-validator.py +1 -22
  152. package/src/scripts/nexo-migrate.py +9 -3
  153. package/src/scripts/nexo-postmortem-consolidator.py +17 -10
  154. package/src/scripts/nexo-pre-commit.py +3 -1
  155. package/src/scripts/nexo-prevent-sleep.sh +29 -0
  156. package/src/scripts/nexo-proactive-dashboard.py +5 -4
  157. package/src/scripts/nexo-runtime-preflight.py +59 -55
  158. package/src/scripts/nexo-send-email.py +1 -1
  159. package/src/scripts/nexo-send-reply.py +3 -1
  160. package/src/scripts/nexo-sleep.py +13 -9
  161. package/src/scripts/nexo-snapshot-restore.sh +2 -1
  162. package/src/scripts/nexo-synthesis.py +11 -7
  163. package/src/scripts/nexo-tcc-approve.sh +79 -0
  164. package/src/scripts/nexo-update.sh +161 -0
  165. package/src/scripts/nexo-watchdog-smoke.py +18 -13
  166. package/src/scripts/nexo-watchdog.sh +22 -13
  167. package/src/server.py +77 -28
  168. package/src/storage_router.py +6 -2
  169. package/src/tools_learnings.py +6 -6
  170. package/src/tools_menu.py +1 -1
  171. package/src/tools_reminders_crud.py +10 -8
  172. package/src/tools_sessions.py +76 -4
  173. package/templates/CLAUDE.md.template +14 -80
  174. package/templates/launchagents/README.md +7 -7
  175. package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
  176. package/templates/launchagents/com.nexo.catchup.plist +4 -0
  177. package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
  178. package/templates/launchagents/com.nexo.dashboard.plist +5 -1
  179. package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
  180. package/templates/launchagents/com.nexo.evolution.plist +4 -0
  181. package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
  182. package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
  183. package/templates/launchagents/com.nexo.immune.plist +4 -0
  184. package/templates/launchagents/com.nexo.postmortem.plist +4 -0
  185. package/templates/launchagents/com.nexo.self-audit.plist +4 -0
  186. package/templates/launchagents/com.nexo.synthesis.plist +4 -0
  187. package/templates/launchagents/com.nexo.watchdog.plist +4 -0
  188. package/templates/openclaw.json +1 -1
  189. package/tests/conftest.py +2 -2
  190. package/tests/test_cognitive.py +7 -6
  191. package/tests/test_migrations.py +26 -0
  192. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  193. package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
  194. package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
  195. package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
  196. package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  197. package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
  198. package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
  199. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  200. package/src/__pycache__/server.cpython-314.pyc +0 -0
  201. package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
  202. package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
  203. package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
  204. package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
  205. package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
  206. package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
  207. package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
  208. package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
  209. package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
  210. package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
  212. package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
  213. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  214. package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
  215. package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
  216. package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
  217. package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
  218. package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
  219. package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
  220. package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
  221. package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
  222. package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
  223. package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
  224. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
  225. package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
  226. package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
  227. package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  244. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  245. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  246. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  247. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  248. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  249. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  250. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  251. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
  252. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
  253. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
  254. package/src/scripts/deep-sleep/analyze_session.py +0 -217
  255. package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
  256. package/src/scripts/deep-sleep/prompt.md +0 -109
  257. package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  259. package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
  260. package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
  261. package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -27,6 +27,7 @@ Options:
27
27
  import argparse
28
28
  import imaplib
29
29
  import json
30
+ import os
30
31
  import smtplib
31
32
  import sys
32
33
  import time
@@ -35,7 +36,8 @@ from email.utils import make_msgid, formatdate
35
36
  from pathlib import Path
36
37
  import mimetypes
37
38
 
38
- CONFIG_PATH = Path.home() / ".nexo" / "nexo-email" / "config.json"
39
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
40
+ CONFIG_PATH = NEXO_HOME / "nexo-email" / "config.json"
39
41
 
40
42
 
41
43
  def load_config():
@@ -32,7 +32,7 @@ from pathlib import Path
32
32
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
33
33
 
34
34
  # ─── Paths ────────────────────────────────────────────────────────────────────
35
- CLAUDE_DIR = Path.home() / ".nexo"
35
+ CLAUDE_DIR = NEXO_HOME
36
36
  BRAIN_DIR = CLAUDE_DIR / "brain"
37
37
  COORD_DIR = CLAUDE_DIR / "coordination"
38
38
  MEMORY_DIR = CLAUDE_DIR / "memory"
@@ -46,8 +46,8 @@ HEARTBEAT_LOG = COORD_DIR / "heartbeat-log.json"
46
46
  REFLECTION_LOG = COORD_DIR / "reflection-log.json"
47
47
  SLEEP_LOG = COORD_DIR / "sleep-log.json"
48
48
 
49
- MEMORY_MD = Path.home() / ".nexo" / "memory" / "MEMORY.md"
50
- NEXO_DB = Path.home() / ".nexo" / "nexo.db"
49
+ MEMORY_MD = NEXO_HOME / "memory" / "MEMORY.md"
50
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
51
51
  CLAUDE_MEM_DB = Path.home() / ".claude-mem" / "claude-mem.db"
52
52
  CLAUDE_CLI = Path.home() / ".local" / "bin" / "claude"
53
53
 
@@ -252,7 +252,7 @@ def stage_a_cleanup() -> dict:
252
252
 
253
253
  # A8: Delete cortex/logs/*.log >7 days, truncate launchd >5MB
254
254
  cutoff_7 = TODAY - timedelta(days=7)
255
- cortex_logs = Path.home() / ".nexo" / "cortex" / "logs"
255
+ cortex_logs = NEXO_HOME / "cortex" / "logs"
256
256
  if cortex_logs.exists():
257
257
  for f in cortex_logs.glob("*.log"):
258
258
  if f.name.startswith("launchd-"):
@@ -383,9 +383,12 @@ The wrapper will handle the actual DB cleanup safely.""")
383
383
 
384
384
  tasks_str = "\n\n".join(tasks)
385
385
 
386
- prompt = f"""You are NEXO Sleep the nightly brain maintenance process.
386
+ prompt = f"""FIRST: Call nexo_startup(task='deep-sleep nightly maintenance') to register this session.
387
+
388
+ You are NEXO Sleep — the nightly brain maintenance process.
387
389
  Like a human brain during sleep: consolidate important memories, discard noise,
388
390
  detect conflicts, prepare state for tomorrow.
391
+ Use nexo_learning_add, nexo_followup_create, nexo_session_diary_write and other MCP tools directly.
389
392
 
390
393
  BRAIN STATE:
391
394
  - {len(state['learnings'])} active learnings
@@ -407,16 +410,17 @@ Write a summary to {COORD_DIR}/sleep-report.md when done.
407
410
  Execute without asking."""
408
411
 
409
412
  log("Stage B: Invoking Claude CLI (opus) — dreaming...")
410
-
411
413
  env = os.environ.copy()
414
+ env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
412
415
  env.pop("CLAUDECODE", None)
413
416
  env.pop("CLAUDE_CODE", None)
414
417
 
415
418
  try:
416
419
  result = subprocess.run(
417
420
  [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
418
- "--allowedTools", "Read,Write,Edit,Glob,Grep"],
419
- capture_output=True, text=True, timeout=600, env=env
421
+ "--output-format", "text",
422
+ "--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
423
+ capture_output=True, text=True, timeout=21600, env=env
420
424
  )
421
425
 
422
426
  if result.returncode != 0:
@@ -568,7 +572,7 @@ def main():
568
572
 
569
573
  # Register for catch-up
570
574
  try:
571
- state_file = Path.home() / ".nexo" / "operations" / ".catchup-state.json"
575
+ state_file = NEXO_HOME / "operations" / ".catchup-state.json"
572
576
  st = json.loads(state_file.read_text()) if state_file.exists() else {}
573
577
  st["sleep"] = datetime.now().isoformat()
574
578
  state_file.write_text(json.dumps(st, indent=2))
@@ -5,7 +5,8 @@ set -euo pipefail
5
5
 
6
6
  SNAP_DIR="${1:?Usage: nexo-snapshot-restore.sh <snapshot-dir>}"
7
7
  MANIFEST="$SNAP_DIR/manifest.json"
8
- RESTORE_LOG="$HOME/claude/logs/snapshot-restores.log"
8
+ NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
9
+ RESTORE_LOG="$NEXO_HOME/logs/snapshot-restores.log"
9
10
 
10
11
  if [ ! -f "$MANIFEST" ]; then
11
12
  echo "ERROR: No manifest.json in $SNAP_DIR" >&2
@@ -19,9 +19,10 @@ from datetime import datetime, date, timedelta
19
19
  from pathlib import Path
20
20
 
21
21
  HOME = Path.home()
22
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
22
23
  CLAUDE_DIR = NEXO_HOME
23
24
  COORD_DIR = CLAUDE_DIR / "coordination"
24
- NEXO_DB = NEXO_HOME / "nexo-mcp" / "db" / "nexo.db"
25
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
25
26
  OUTPUT_FILE = COORD_DIR / "daily-synthesis.md"
26
27
  LAST_RUN_FILE = COORD_DIR / "synthesis-last-run"
27
28
  LOCK_FILE = COORD_DIR / "synthesis.lock"
@@ -142,9 +143,11 @@ def synthesize(data: dict) -> bool:
142
143
  if len(data_json) > 15000:
143
144
  data_json = data_json[:15000] + "\n... (truncated)"
144
145
 
145
- prompt = f"""You are NEXO's synthesis engine. Write the daily intelligence brief for tomorrow's
146
+ prompt = f"""FIRST: Call nexo_startup(task='daily synthesis') to register this session.
147
+
148
+ You are NEXO's synthesis engine. Write the daily intelligence brief for tomorrow's
146
149
  startup. This file is read by NEXO at the beginning of each session to understand
147
- what happened today and what to focus on tomorrow.
150
+ what happened today and what to focus on tomorrow. Use nexo_learning_add and nexo_followup_create if you discover actionable items.
148
151
 
149
152
  TODAY'S RAW DATA:
150
153
  {data_json}
@@ -181,17 +184,18 @@ not what merely happened. If a section has nothing, write "Nothing notable."
181
184
 
182
185
  Execute without asking."""
183
186
 
184
- log("Invoking Claude CLI (sonnet) for synthesis...")
185
-
187
+ log("Invoking Claude CLI (opus) for synthesis...")
186
188
  env = os.environ.copy()
189
+ env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
187
190
  env.pop("CLAUDECODE", None)
188
191
  env.pop("CLAUDE_CODE", None)
189
192
 
190
193
  try:
191
194
  result = subprocess.run(
192
195
  [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
193
- "--allowedTools", "Read,Write,Edit,Glob,Grep"],
194
- capture_output=True, text=True, timeout=180, env=env
196
+ "--output-format", "text",
197
+ "--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
198
+ capture_output=True, text=True, timeout=21600, env=env
195
199
  )
196
200
 
197
201
  if result.returncode != 0:
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+ # NEXO TCC Auto-Approve — grants macOS permissions to new Claude Code versions.
3
+ #
4
+ # macOS only. On Linux this is a no-op (Linux doesn't have TCC).
5
+ # Runs at load to approve any new Claude versions that appeared.
6
+ #
7
+ # What it does:
8
+ # 1. Scans ~/.local/share/claude/versions/ for Claude binaries
9
+ # 2. For each new version, grants TCC access to Documents, Desktop, Downloads, etc.
10
+ # 3. Also approves the Python binary used by NEXO's venv
11
+ # 4. Tracks which versions have been approved to avoid re-processing
12
+ #
13
+ # Why: Claude Code updates frequently. Each new binary needs macOS permission
14
+ # grants or the user gets popup dialogs interrupting their work.
15
+
16
+ set -euo pipefail
17
+
18
+ # Linux: nothing to do
19
+ if [ "$(uname -s)" != "Darwin" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
24
+ TCC_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
25
+ VERSIONS_DIR="$HOME/.local/share/claude/versions"
26
+ MARKER_DIR="$NEXO_HOME/data/.tcc-approved"
27
+ LOG="$NEXO_HOME/logs/tcc-auto-approve.log"
28
+
29
+ mkdir -p "$MARKER_DIR" "$(dirname "$LOG")"
30
+
31
+ # TCC services Claude Code needs
32
+ SERVICES=(
33
+ kTCCServiceSystemPolicyDocumentsFolder
34
+ kTCCServiceSystemPolicyDesktopFolder
35
+ kTCCServiceSystemPolicyDownloadsFolder
36
+ kTCCServiceMediaLibrary
37
+ kTCCServiceSystemPolicyNetworkVolumes
38
+ kTCCServiceSystemPolicyAppData
39
+ kTCCServiceFileProviderDomain
40
+ )
41
+
42
+ # Approve Claude versions
43
+ if [ -d "$VERSIONS_DIR" ]; then
44
+ for bin_path in "$VERSIONS_DIR"/*; do
45
+ [ ! -e "$bin_path" ] && continue
46
+ version=$(basename "$bin_path")
47
+ marker="$MARKER_DIR/$version"
48
+
49
+ # Skip if already approved
50
+ [ -f "$marker" ] && continue
51
+
52
+ echo "$(date '+%Y-%m-%d %H:%M:%S') Approving Claude $version" >> "$LOG"
53
+
54
+ for svc in "${SERVICES[@]}"; do
55
+ sqlite3 "$TCC_DB" "
56
+ INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version)
57
+ VALUES ('$svc', '$bin_path', 1, 2, 4, 1);
58
+ " 2>/dev/null
59
+ done
60
+
61
+ touch "$marker"
62
+ echo "$(date '+%Y-%m-%d %H:%M:%S') Done: Claude $version — ${#SERVICES[@]} services approved" >> "$LOG"
63
+ done
64
+ fi
65
+
66
+ # Also approve Python from NEXO's venv (if it exists)
67
+ NEXO_CODE="${NEXO_CODE:-}"
68
+ if [ -n "$NEXO_CODE" ]; then
69
+ PYTHON_BIN="$(dirname "$NEXO_CODE")/.venv/bin/python"
70
+ if [ -e "$PYTHON_BIN" ]; then
71
+ PYTHON_REAL=$(readlink -f "$PYTHON_BIN" 2>/dev/null || echo "$PYTHON_BIN")
72
+ for svc in "${SERVICES[@]}"; do
73
+ sqlite3 "$TCC_DB" "
74
+ INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version)
75
+ VALUES ('$svc', '$PYTHON_REAL', 1, 2, 4, 1);
76
+ " 2>/dev/null
77
+ done
78
+ fi
79
+ fi
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ # nexo-update.sh — Standalone NEXO update script
3
+ # Same logic as the MCP tool but usable when the server itself needs updating.
4
+ #
5
+ # Usage:
6
+ # nexo-update.sh # pull from origin main
7
+ # nexo-update.sh origin beta # pull from origin beta
8
+ # NEXO_HOME=/path nexo-update.sh # custom NEXO_HOME
9
+
10
+ set -euo pipefail
11
+
12
+ # --- Configuration ---
13
+ REMOTE="${1:-origin}"
14
+ BRANCH="${2:-main}"
15
+ NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
16
+
17
+ # Determine repo directory: script is at src/scripts/, repo root is ../../
18
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19
+ REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
20
+ SRC_DIR="$REPO_DIR/src"
21
+ PACKAGE_JSON="$REPO_DIR/package.json"
22
+
23
+ # --- Helpers ---
24
+ RED='\033[0;31m'
25
+ GREEN='\033[0;32m'
26
+ YELLOW='\033[1;33m'
27
+ NC='\033[0m'
28
+
29
+ log() { echo -e "${GREEN}[nexo-update]${NC} $*"; }
30
+ warn() { echo -e "${YELLOW}[nexo-update]${NC} $*"; }
31
+ err() { echo -e "${RED}[nexo-update]${NC} $*" >&2; }
32
+
33
+ read_version() {
34
+ python3 -c "import json; print(json.load(open('$PACKAGE_JSON')).get('version','unknown'))" 2>/dev/null || echo "unknown"
35
+ }
36
+
37
+ # --- Step 1: Check for uncommitted changes in src/ ---
38
+ log "Checking for uncommitted changes in src/..."
39
+ cd "$REPO_DIR"
40
+
41
+ if [ -n "$(git status --porcelain -- src/ 2>/dev/null)" ]; then
42
+ err "ABORTED: Uncommitted changes in src/"
43
+ git status --short -- src/
44
+ exit 1
45
+ fi
46
+ log "Working tree clean."
47
+
48
+ # Record current state
49
+ OLD_VERSION="$(read_version)"
50
+ OLD_COMMIT="$(git rev-parse HEAD)"
51
+ log "Current: v${OLD_VERSION} (${OLD_COMMIT:0:8})"
52
+
53
+ # --- Step 2: Backup databases ---
54
+ TIMESTAMP="$(date +%Y-%m-%d-%H%M)"
55
+ BACKUP_DIR="$NEXO_HOME/backups/pre-update-$TIMESTAMP"
56
+
57
+ backup_dbs() {
58
+ local found=0
59
+ # Check data/, NEXO_HOME root, and src/ for .db files
60
+ for dir in "$NEXO_HOME/data" "$NEXO_HOME" "$SRC_DIR"; do
61
+ if [ -d "$dir" ]; then
62
+ for db in "$dir"/*.db; do
63
+ [ -f "$db" ] || continue
64
+ found=1
65
+ mkdir -p "$BACKUP_DIR"
66
+ cp "$db" "$BACKUP_DIR/$(basename "$db")"
67
+ log " Backed up: $(basename "$db")"
68
+ done
69
+ fi
70
+ done
71
+ if [ "$found" -eq 0 ]; then
72
+ log " No databases found to backup."
73
+ fi
74
+ }
75
+
76
+ log "Backing up databases..."
77
+ backup_dbs
78
+
79
+ # --- Step 3: git pull ---
80
+ log "Pulling from ${REMOTE}/${BRANCH}..."
81
+ PULL_OUTPUT="$(git pull "$REMOTE" "$BRANCH" 2>&1)" || {
82
+ err "git pull failed:"
83
+ err "$PULL_OUTPUT"
84
+ exit 1
85
+ }
86
+ log "$PULL_OUTPUT"
87
+
88
+ if echo "$PULL_OUTPUT" | grep -q "Already up to date"; then
89
+ log "Already up to date (v${OLD_VERSION}). Done."
90
+ exit 0
91
+ fi
92
+
93
+ # --- Step 4: Check version ---
94
+ NEW_VERSION="$(read_version)"
95
+ log "New version: v${NEW_VERSION}"
96
+
97
+ # --- Step 5: Run migrations if version changed ---
98
+ if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
99
+ log "Version changed: ${OLD_VERSION} -> ${NEW_VERSION}"
100
+ log "Running migrations..."
101
+ if ! (cd "$SRC_DIR" && python3 -c "import db; db.init_db()" 2>&1); then
102
+ err "Migration failed! Rolling back..."
103
+ git reset --hard "$OLD_COMMIT"
104
+ # Restore DB backups
105
+ if [ -d "$BACKUP_DIR" ]; then
106
+ for db in "$BACKUP_DIR"/*.db; do
107
+ [ -f "$db" ] || continue
108
+ BASENAME="$(basename "$db")"
109
+ for candidate in "$NEXO_HOME/data/$BASENAME" "$NEXO_HOME/$BASENAME" "$SRC_DIR/$BASENAME"; do
110
+ if [ -f "$candidate" ]; then
111
+ cp "$db" "$candidate"
112
+ warn " Restored: $BASENAME"
113
+ break
114
+ fi
115
+ done
116
+ done
117
+ fi
118
+ err "Rolled back to ${OLD_COMMIT:0:8}. Databases restored."
119
+ exit 1
120
+ fi
121
+ log "Migrations applied."
122
+ else
123
+ log "Version unchanged (${OLD_VERSION}), skipping migrations."
124
+ fi
125
+
126
+ # --- Step 6: Verify import ---
127
+ log "Verifying server.py import..."
128
+ if ! (cd "$SRC_DIR" && python3 -c "import server" 2>&1); then
129
+ err "Import verification failed! Rolling back..."
130
+ git reset --hard "$OLD_COMMIT"
131
+ if [ -d "$BACKUP_DIR" ]; then
132
+ for db in "$BACKUP_DIR"/*.db; do
133
+ [ -f "$db" ] || continue
134
+ BASENAME="$(basename "$db")"
135
+ for candidate in "$NEXO_HOME/data/$BASENAME" "$NEXO_HOME/$BASENAME" "$SRC_DIR/$BASENAME"; do
136
+ if [ -f "$candidate" ]; then
137
+ cp "$db" "$candidate"
138
+ warn " Restored: $BASENAME"
139
+ break
140
+ fi
141
+ done
142
+ done
143
+ fi
144
+ err "Rolled back to ${OLD_COMMIT:0:8}. Databases restored."
145
+ exit 1
146
+ fi
147
+
148
+ # --- Done ---
149
+ echo ""
150
+ log "========================================="
151
+ log " UPDATE SUCCESSFUL"
152
+ if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
153
+ log " Version: ${OLD_VERSION} -> ${NEW_VERSION}"
154
+ else
155
+ log " Version: ${OLD_VERSION} (unchanged)"
156
+ fi
157
+ log " Branch: ${REMOTE}/${BRANCH}"
158
+ log " Backup: ${BACKUP_DIR}"
159
+ log "========================================="
160
+ echo ""
161
+ warn "MCP server restart needed to load new code."
@@ -10,18 +10,19 @@ from __future__ import annotations
10
10
 
11
11
  import hashlib
12
12
  import json
13
+ import os
13
14
  import sqlite3
14
15
  import subprocess
15
16
  from datetime import datetime
16
17
  from pathlib import Path
17
18
 
18
19
  HOME = Path.home()
19
- CLAUDE_DIR = HOME / ".nexo"
20
- NEXO_DIR = CLAUDE_DIR / "nexo-mcp"
21
- CORTEX_DIR = CLAUDE_DIR / "cortex"
22
- LOG_DIR = CLAUDE_DIR / "logs"
20
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(HOME / ".nexo")))
21
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(NEXO_HOME)))
22
+ BRAIN_DIR = NEXO_HOME / "brain"
23
+ LOG_DIR = NEXO_HOME / "logs"
23
24
  SUMMARY_FILE = LOG_DIR / "watchdog-smoke-summary.json"
24
- HASH_REGISTRY = CLAUDE_DIR / "scripts" / ".watchdog-hashes"
25
+ HASH_REGISTRY = NEXO_HOME / "scripts" / ".watchdog-hashes"
25
26
  RESTORE_LOG = LOG_DIR / "snapshot-restores.log"
26
27
 
27
28
 
@@ -41,7 +42,7 @@ def main() -> int:
41
42
  LOG_DIR.mkdir(parents=True, exist_ok=True)
42
43
  findings = []
43
44
 
44
- db_path = NEXO_DIR / "nexo.db"
45
+ db_path = NEXO_HOME / "data" / "nexo.db"
45
46
  integrity = "missing"
46
47
  if db_path.exists():
47
48
  try:
@@ -53,15 +54,16 @@ def main() -> int:
53
54
  if integrity != "ok":
54
55
  findings.append({"severity": "ERROR", "area": "sqlite", "msg": f"integrity={integrity}"})
55
56
 
56
- cortex_running = subprocess.run(
57
- ["pgrep", "-f", "cortex-wrapper.py"],
57
+ # Check if the NEXO MCP server process is alive (replaces legacy cortex process check)
58
+ nexo_server_running = subprocess.run(
59
+ ["pgrep", "-f", "nexo-brain"],
58
60
  capture_output=True,
59
61
  text=True,
60
62
  ).returncode == 0
61
- if not cortex_running:
62
- findings.append({"severity": "WARN", "area": "cortex", "msg": "cortex-wrapper.py not running"})
63
+ if not nexo_server_running:
64
+ findings.append({"severity": "INFO", "area": "server", "msg": "nexo-brain not running (normal if no active session)"})
63
65
 
64
- backups = sorted((NEXO_DIR / "backups").glob("nexo-*.db"), key=lambda p: p.stat().st_mtime, reverse=True)
66
+ backups = sorted((NEXO_HOME / "backups").glob("nexo-*.db"), key=lambda p: p.stat().st_mtime, reverse=True)
65
67
  if backups:
66
68
  age_seconds = int(datetime.now().timestamp() - backups[0].stat().st_mtime)
67
69
  if age_seconds > 7200:
@@ -84,7 +86,10 @@ def main() -> int:
84
86
  elif restore_count > 0:
85
87
  findings.append({"severity": "INFO", "area": "restore_activity", "msg": f"{restore_count} restores this hour"})
86
88
 
87
- objective = CORTEX_DIR / "evolution-objective.json"
89
+ # Check brain/ (canonical) first, fall back to cortex/ (legacy)
90
+ objective = BRAIN_DIR / "evolution-objective.json"
91
+ if not objective.exists():
92
+ objective = NEXO_HOME / "cortex" / "evolution-objective.json"
88
93
  evolution_enabled = None
89
94
  if objective.exists():
90
95
  obj = json.loads(objective.read_text())
@@ -100,7 +105,7 @@ def main() -> int:
100
105
  "timestamp": datetime.now().isoformat(),
101
106
  "ok": not any(f["severity"] == "ERROR" for f in findings),
102
107
  "integrity": integrity,
103
- "cortex_running": cortex_running,
108
+ "server_running": nexo_server_running,
104
109
  "evolution_enabled": evolution_enabled,
105
110
  "restore_count_current_hour": restore_count,
106
111
  "findings": findings,
@@ -13,7 +13,7 @@ set -uo pipefail
13
13
  HOME_DIR="$HOME"
14
14
  NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
15
15
  NEXO_DIR="$NEXO_HOME"
16
- CORTEX_DIR="$NEXO_HOME/cortex"
16
+ CORTEX_DIR="$NEXO_HOME/brain"
17
17
  OPS_DIR="$NEXO_HOME/operations"
18
18
  LOG_DIR="$NEXO_HOME/logs"
19
19
  LOG="$LOG_DIR/watchdog.log"
@@ -49,12 +49,12 @@ MONITORS=(
49
49
  "Cognitive Decay|com.nexo.cognitive-decay|$NEXO_HOME/logs/cognitive-decay-stdout.log|$NEXO_HOME/logs/cognitive-decay-stderr.log|90000||Daily 3:00 AM|core"
50
50
  "Evolution|com.nexo.evolution|$NEXO_HOME/logs/evolution-stdout.log|$NEXO_HOME/logs/evolution-stderr.log|0||Weekly Sun 3:00 AM|core"
51
51
  "GitHub Monitor|com.nexo.github-monitor|$NEXO_HOME/logs/github-monitor-stdout.log|$NEXO_HOME/logs/github-monitor-stderr.log|90000||Daily 8:00 AM|core"
52
- "Immune|com.nexo.immune|$NEXO_HOME/coordination/immune-stdout.log|$NEXO_HOME/coordination/immune-stderr.log|3600||Every 30 min|core"
52
+ "Immune|com.nexo.immune|$NEXO_HOME/logs/immune-stdout.log|$NEXO_HOME/logs/immune-stderr.log|3600||Every 30 min|core"
53
53
  "Postmortem|com.nexo.postmortem|$NEXO_HOME/logs/postmortem-stdout.log|$NEXO_HOME/logs/postmortem-stderr.log|90000||Daily 23:30|core"
54
54
  "Prevent Sleep|com.nexo.prevent-sleep|||0|caffeinate|KeepAlive|core"
55
55
  "Self Audit|com.nexo.self-audit|$NEXO_HOME/logs/self-audit-stdout.log|$NEXO_HOME/logs/self-audit-stderr.log|90000||Daily 7:00 AM|core"
56
- "Sleep|com.nexo.sleep|$NEXO_HOME/coordination/sleep-stdout.log|$NEXO_HOME/coordination/sleep-stderr.log|90000||Daily 4:00 AM|core"
57
- "Synthesis|com.nexo.synthesis|$NEXO_HOME/coordination/synthesis-stdout.log|$NEXO_HOME/coordination/synthesis-stderr.log|10800||Every 2 hours|core"
56
+ "Sleep|com.nexo.sleep|$NEXO_HOME/logs/sleep-stdout.log|$NEXO_HOME/logs/sleep-stderr.log|90000||Daily 4:00 AM|core"
57
+ "Synthesis|com.nexo.synthesis|$NEXO_HOME/logs/synthesis-stdout.log|$NEXO_HOME/logs/synthesis-stderr.log|10800||Every 2 hours|core"
58
58
  "Deep Sleep|com.nexo.deep-sleep|$NEXO_HOME/logs/deep-sleep-stdout.log|$NEXO_HOME/logs/deep-sleep-stderr.log|90000||Daily 4:30 AM|core"
59
59
  "Followup Hygiene|com.nexo.followup-hygiene|$NEXO_HOME/logs/followup-hygiene-stdout.log|$NEXO_HOME/logs/followup-hygiene-stderr.log|604800||Weekly Sun 5:00 AM|core"
60
60
  # Add your own personal monitors below (type "personal"):
@@ -76,11 +76,13 @@ ERROR_PATTERNS="Traceback|Error:|CRITICAL|FATAL|ModuleNotFoundError|PermissionEr
76
76
  UID_NUM=$(id -u)
77
77
  REPAIR_LOG="$LOG_DIR/watchdog-repairs.log"
78
78
  TOTAL_HEALED=0
79
+ IS_MACOS=false
80
+ [ "$(uname)" = "Darwin" ] && IS_MACOS=true
79
81
 
80
82
  log_repair() { echo "[$TS] REPAIR: $1" >> "$REPAIR_LOG"; log "REPAIR: $1"; }
81
83
 
82
84
  is_loaded() {
83
- launchctl list "$1" &>/dev/null
85
+ $IS_MACOS && launchctl list "$1" &>/dev/null
84
86
  }
85
87
 
86
88
  # ============================================================================
@@ -88,6 +90,7 @@ is_loaded() {
88
90
  # ============================================================================
89
91
 
90
92
  try_repair_launchagent() {
93
+ $IS_MACOS || return 1
91
94
  local plist_id="$1"
92
95
  local proc_grep="$2"
93
96
  local plist_file="$HOME_DIR/Library/LaunchAgents/${plist_id}.plist"
@@ -134,6 +137,7 @@ try_repair_cron() {
134
137
  }
135
138
 
136
139
  try_reexecute_missed_cron() {
140
+ $IS_MACOS || return 1
137
141
  # Re-execute a cron that missed its scheduled run
138
142
  # Extracts ProgramArguments from the plist and runs them
139
143
  local plist_id="$1"
@@ -184,6 +188,7 @@ except:
184
188
  }
185
189
 
186
190
  try_verify_repair() {
191
+ $IS_MACOS || return 1
187
192
  # After Level 2 repair, wait and verify the service is healthy
188
193
  local plist_id="$1"
189
194
  local log_stdout="$2"
@@ -236,7 +241,7 @@ try_repair_backup() {
236
241
  local newest
237
242
  newest=$(ls -t "$NEXO_DIR/backups/nexo-"*.db 2>/dev/null | head -1)
238
243
  if [ -n "$newest" ]; then
239
- local age=$(( TS_EPOCH - $(stat -f %m "$newest") ))
244
+ if $IS_MACOS; then local age=$(( TS_EPOCH - $(stat -f %m "$newest") )); else local age=$(( TS_EPOCH - $(stat -c %Y "$newest") )); fi
240
245
  if [ "$age" -lt 60 ]; then
241
246
  log_repair "backup_cron.sh: ran successfully, fresh backup created"
242
247
  return 0
@@ -249,7 +254,11 @@ try_repair_backup() {
249
254
  file_age() {
250
255
  if [ -f "$1" ]; then
251
256
  local mod_epoch
252
- mod_epoch=$(stat -f %m "$1" 2>/dev/null || echo 0)
257
+ if $IS_MACOS; then
258
+ mod_epoch=$(stat -f %m "$1" 2>/dev/null || echo 0)
259
+ else
260
+ mod_epoch=$(stat -c %Y "$1" 2>/dev/null || echo 0)
261
+ fi
253
262
  echo $(( TS_EPOCH - mod_epoch ))
254
263
  else
255
264
  echo 999999
@@ -511,7 +520,7 @@ done
511
520
  # --- SQLite integrity ---
512
521
  SQLITE_STATUS="PASS"
513
522
  SQLITE_DETAIL=""
514
- INTEGRITY=$(sqlite3 "$NEXO_DIR/nexo.db" "PRAGMA integrity_check;" 2>/dev/null || echo "CORRUPT")
523
+ INTEGRITY=$(sqlite3 "$NEXO_DIR/data/nexo.db" "PRAGMA integrity_check;" 2>/dev/null || echo "CORRUPT")
515
524
  if [ "$INTEGRITY" != "ok" ]; then
516
525
  SQLITE_STATUS="FAIL"
517
526
  SQLITE_DETAIL="Integrity check: $INTEGRITY"
@@ -519,7 +528,7 @@ if [ "$INTEGRITY" != "ok" ]; then
519
528
  TOTAL_FAIL=$((TOTAL_FAIL + 1))
520
529
  LATEST_BACKUP=$(ls -t "$NEXO_DIR/backups/nexo-"*.db 2>/dev/null | head -1)
521
530
  if [ -n "$LATEST_BACKUP" ]; then
522
- cp "$LATEST_BACKUP" "$NEXO_DIR/nexo.db"
531
+ cp "$LATEST_BACKUP" "$NEXO_DIR/data/nexo.db"
523
532
  log "RESTORED from $LATEST_BACKUP"
524
533
  SQLITE_DETAIL="${SQLITE_DETAIL}. Restored from backup."
525
534
  fi
@@ -576,7 +585,7 @@ BACKUP_STATUS="PASS"
576
585
  BACKUP_DETAIL=""
577
586
  LATEST_BACKUP=$(ls -t "$NEXO_DIR/backups/nexo-"*.db 2>/dev/null | head -1)
578
587
  if [ -n "$LATEST_BACKUP" ]; then
579
- BACKUP_AGE=$(( TS_EPOCH - $(stat -f %m "$LATEST_BACKUP") ))
588
+ if $IS_MACOS; then BACKUP_AGE=$(( TS_EPOCH - $(stat -f %m "$LATEST_BACKUP") )); else BACKUP_AGE=$(( TS_EPOCH - $(stat -c %Y "$LATEST_BACKUP") )); fi
580
589
  BACKUP_AGE_STR=$(format_age "$BACKUP_AGE")
581
590
  if [ "$BACKUP_AGE" -gt 7200 ]; then
582
591
  # AUTO-REPAIR: run backup now
@@ -603,7 +612,7 @@ fi
603
612
  # --- Cognitive DB check ---
604
613
  COG_STATUS="PASS"
605
614
  COG_DETAIL=""
606
- COG_DB="$NEXO_DIR/cognitive.db"
615
+ COG_DB="$NEXO_DIR/data/cognitive.db"
607
616
  if [ -f "$COG_DB" ]; then
608
617
  COG_INT=$(sqlite3 "$COG_DB" "PRAGMA integrity_check;" 2>/dev/null || echo "CORRUPT")
609
618
  if [ "$COG_INT" != "ok" ]; then
@@ -724,7 +733,7 @@ if [ "$TOTAL_FAIL" -gt 0 ]; then
724
733
  LOCK_AGE=999999
725
734
  SKIP_REPAIR=false
726
735
  if [ -f "$REPAIR_LOCK" ]; then
727
- LOCK_AGE=$(( TS_EPOCH - $(stat -f %m "$REPAIR_LOCK" 2>/dev/null || echo 0) ))
736
+ if $IS_MACOS; then LOCK_AGE=$(( TS_EPOCH - $(stat -f %m "$REPAIR_LOCK" 2>/dev/null || echo 0) )); else LOCK_AGE=$(( TS_EPOCH - $(stat -c %Y "$REPAIR_LOCK" 2>/dev/null || echo 0) )); fi
728
737
  if [ "$LOCK_AGE" -lt "$REPAIR_COOLDOWN" ]; then
729
738
  log "NEXO repair skipped: cooldown (${LOCK_AGE}s < ${REPAIR_COOLDOWN}s)"
730
739
  SKIP_REPAIR=true
@@ -773,7 +782,7 @@ ${STDOUT_TAIL}
773
782
  if $HAS_CORE_FAILS && [ -n "$NEXO_PUBLIC_REPO" ] && [ -d "$NEXO_PUBLIC_REPO/.git" ]; then
774
783
  PROPAGATE_BLOCK="
775
784
  PROPAGATION (for [core] fixes ONLY):
776
- If your fix modifies a file under $NEXO_HOME/ (server.py, db.py, plugins/, scripts/):
785
+ If your fix modifies a file under $NEXO_HOME/ (server.py, db/, plugins/, scripts/):
777
786
  1. Commit the fix locally with a descriptive message
778
787
  2. Copy the changed files (sanitized — no personal data) to $NEXO_PUBLIC_REPO/src/
779
788
  3. Bump patch version in $NEXO_PUBLIC_REPO/package.json