nexo-brain 2.3.0 → 2.3.2

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 (299) hide show
  1. package/README.md +1 -1
  2. package/bin/nexo-brain.js +92 -9
  3. package/bin/postinstall.js +22 -15
  4. package/package.json +7 -4
  5. package/src/auto_update.py +194 -5
  6. package/src/crons/sync.py +6 -2
  7. package/src/db/_core.py +1 -0
  8. package/src/db/_entities.py +1 -0
  9. package/src/db/_episodic.py +1 -0
  10. package/src/db/_learnings.py +1 -0
  11. package/src/db/_reminders.py +1 -0
  12. package/src/db/_schema.py +11 -1
  13. package/src/db/_sessions.py +1 -0
  14. package/src/db/_skills.py +1 -0
  15. package/src/hooks/capture-tool-logs.sh +23 -6
  16. package/src/hooks/session-start.sh +4 -3
  17. package/src/plugin_loader.py +1 -0
  18. package/src/plugins/update.py +377 -26
  19. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  20. package/src/scripts/deep-sleep/collect.py +1 -0
  21. package/src/scripts/deep-sleep/extract.py +1 -0
  22. package/src/scripts/deep-sleep/synthesize.py +1 -0
  23. package/src/scripts/nexo-catchup.py +29 -4
  24. package/src/scripts/nexo-daily-self-audit.py +21 -1
  25. package/src/scripts/nexo-evolution-run.py +21 -1
  26. package/src/scripts/nexo-learning-housekeep.py +1 -0
  27. package/src/scripts/nexo-postmortem-consolidator.py +34 -9
  28. package/src/scripts/nexo-sleep.py +32 -10
  29. package/src/scripts/nexo-synthesis.py +29 -9
  30. package/src/scripts/nexo-update.sh +109 -7
  31. package/src/scripts/nexo-watchdog.sh +122 -58
  32. package/src/server.py +66 -1
  33. package/src/tools_coordination.py +1 -0
  34. package/src/tools_sessions.py +1 -0
  35. package/scripts/migrate-to-unified 2.sh +0 -813
  36. package/scripts/migrate-to-unified.sh +0 -813
  37. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  38. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  39. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  40. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  41. package/scripts/nexo-preflight.sh +0 -236
  42. package/scripts/pre-commit-check 2.sh +0 -55
  43. package/scripts/pre-commit-check.sh +0 -55
  44. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  45. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  46. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  47. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  48. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  49. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  50. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  51. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  52. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  53. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  54. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  55. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  56. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  57. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  58. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  59. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  60. package/src/auto_close_sessions 2.py +0 -159
  61. package/src/auto_update 2.py +0 -634
  62. package/src/claim_graph 2.py +0 -323
  63. package/src/cognitive/__init__ 2.py +0 -62
  64. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  73. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  74. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  75. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  76. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  77. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  78. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  79. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  80. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  81. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  82. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  83. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  84. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  85. package/src/cognitive/_core 2.py +0 -567
  86. package/src/cognitive/_decay 2.py +0 -382
  87. package/src/cognitive/_ingest 2.py +0 -892
  88. package/src/cognitive/_memory 2.py +0 -912
  89. package/src/cognitive/_search 2.py +0 -949
  90. package/src/cognitive/_trust 2.py +0 -464
  91. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  92. package/src/crons/manifest 2.json +0 -106
  93. package/src/crons/sync 2.py +0 -217
  94. package/src/dashboard/__init__ 2.py +0 -0
  95. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  96. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  97. package/src/dashboard/app 2.py +0 -789
  98. package/src/db/__init__ 2.py +0 -89
  99. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  100. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  101. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  102. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  103. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  104. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  105. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  106. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  107. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  108. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  109. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  128. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  129. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  130. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  131. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  132. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  133. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  134. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  135. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  136. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  137. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  138. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  139. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  140. package/src/db/_core 2.py +0 -417
  141. package/src/db/_credentials 2.py +0 -124
  142. package/src/db/_entities 2.py +0 -178
  143. package/src/db/_episodic 2.py +0 -738
  144. package/src/db/_evolution 2.py +0 -54
  145. package/src/db/_fts 2.py +0 -406
  146. package/src/db/_learnings 2.py +0 -168
  147. package/src/db/_reminders 2.py +0 -338
  148. package/src/db/_schema 2.py +0 -364
  149. package/src/db/_sessions 2.py +0 -300
  150. package/src/db/_tasks 2.py +0 -91
  151. package/src/evolution_cycle 2.py +0 -266
  152. package/src/hnsw_index 2.py +0 -254
  153. package/src/hooks/auto_capture 2.py +0 -208
  154. package/src/hooks/caffeinate-guard 2.sh +0 -8
  155. package/src/hooks/capture-session 2.sh +0 -21
  156. package/src/hooks/capture-tool-logs 2.sh +0 -127
  157. package/src/hooks/daily-briefing-check 2.sh +0 -33
  158. package/src/hooks/inbox-hook 2.sh +0 -76
  159. package/src/hooks/post-compact 2.sh +0 -148
  160. package/src/hooks/pre-compact 2.sh +0 -151
  161. package/src/hooks/session-start 2.sh +0 -268
  162. package/src/hooks/session-stop 2.sh +0 -140
  163. package/src/kg_populate 2.py +0 -290
  164. package/src/knowledge_graph 2.py +0 -257
  165. package/src/maintenance 2.py +0 -59
  166. package/src/migrate_embeddings 2.py +0 -122
  167. package/src/plugin_loader 2.py +0 -202
  168. package/src/plugins/__init__ 2.py +0 -0
  169. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  172. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  175. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  189. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  191. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  193. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  194. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  195. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  196. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  197. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  198. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  199. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  200. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  201. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  202. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  203. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  204. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  205. package/src/plugins/adaptive_mode 2.py +0 -805
  206. package/src/plugins/agents 2.py +0 -52
  207. package/src/plugins/artifact_registry 2.py +0 -450
  208. package/src/plugins/backup 2.py +0 -104
  209. package/src/plugins/cognitive_memory 2.py +0 -564
  210. package/src/plugins/core_rules 2.py +0 -252
  211. package/src/plugins/cortex 2.py +0 -299
  212. package/src/plugins/entities 2.py +0 -67
  213. package/src/plugins/episodic_memory 2.py +0 -533
  214. package/src/plugins/evolution 2.py +0 -115
  215. package/src/plugins/guard 2.py +0 -746
  216. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  217. package/src/plugins/preferences 2.py +0 -47
  218. package/src/plugins/update 2.py +0 -256
  219. package/src/requirements 2.txt +0 -12
  220. package/src/rules/__init__ 2.py +0 -0
  221. package/src/rules/core-rules 2.json +0 -331
  222. package/src/rules/migrate 2.py +0 -207
  223. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  244. package/src/scripts/check-context 2.py +0 -264
  245. package/src/scripts/nexo-auto-update 2.py +0 -6
  246. package/src/scripts/nexo-backup 2.sh +0 -25
  247. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  248. package/src/scripts/nexo-catchup 2.py +0 -242
  249. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  250. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  251. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  252. package/src/scripts/nexo-evolution-run 2.py +0 -597
  253. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  254. package/src/scripts/nexo-github-monitor 2.py +0 -256
  255. package/src/scripts/nexo-immune 2.py +0 -927
  256. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  257. package/src/scripts/nexo-install 2.py +0 -6
  258. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  259. package/src/scripts/nexo-learning-validator 2.py +0 -207
  260. package/src/scripts/nexo-migrate 2.py +0 -232
  261. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  262. package/src/scripts/nexo-pre-commit 2.py +0 -120
  263. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  264. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  265. package/src/scripts/nexo-reflection 2.py +0 -253
  266. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  267. package/src/scripts/nexo-send-email 2.py +0 -25
  268. package/src/scripts/nexo-send-email.py +0 -25
  269. package/src/scripts/nexo-send-reply 2.py +0 -178
  270. package/src/scripts/nexo-send-reply.py +0 -178
  271. package/src/scripts/nexo-sleep 2.py +0 -592
  272. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  273. package/src/scripts/nexo-synthesis 2.py +0 -253
  274. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  275. package/src/scripts/nexo-update 2.sh +0 -161
  276. package/src/scripts/nexo-watchdog 2.sh +0 -878
  277. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  278. package/src/server 2.py +0 -733
  279. package/src/storage_router 2.py +0 -32
  280. package/src/tools_coordination 2.py +0 -102
  281. package/src/tools_credentials 2.py +0 -68
  282. package/src/tools_learnings 2.py +0 -220
  283. package/src/tools_menu 2.py +0 -227
  284. package/src/tools_reminders 2.py +0 -86
  285. package/src/tools_reminders_crud 2.py +0 -159
  286. package/src/tools_sessions 2.py +0 -476
  287. package/src/tools_task_history 2.py +0 -57
  288. package/templates/CLAUDE.md 2.template +0 -63
  289. package/templates/openclaw 2.json +0 -13
  290. package/tests/__init__ 2.py +0 -0
  291. package/tests/__init__.py +0 -0
  292. package/tests/conftest 2.py +0 -71
  293. package/tests/conftest.py +0 -71
  294. package/tests/test_cognitive 2.py +0 -205
  295. package/tests/test_cognitive.py +0 -205
  296. package/tests/test_knowledge_graph 2.py +0 -140
  297. package/tests/test_knowledge_graph.py +0 -140
  298. package/tests/test_migrations 2.py +0 -137
  299. package/tests/test_migrations.py +0 -137
package/src/server 2.py DELETED
@@ -1,733 +0,0 @@
1
- """NEXO MCP Server — Phase 4: Hot-Reload Plugin System."""
2
-
3
- import os
4
- import signal
5
- import sys
6
-
7
- from fastmcp import FastMCP
8
- from db import init_db, rebuild_fts_index, get_db, close_db, fts_add_dir, fts_remove_dir, fts_list_dirs
9
- from tools_sessions import handle_startup, handle_heartbeat, handle_status, handle_context_packet, handle_smart_startup_query
10
- from tools_coordination import (
11
- handle_track, handle_untrack, handle_files,
12
- handle_send, handle_ask, handle_answer, handle_check_answer,
13
- )
14
- from tools_reminders import handle_reminders
15
- from tools_menu import handle_menu
16
- from tools_reminders_crud import (
17
- handle_reminder_create, handle_reminder_update,
18
- handle_reminder_complete, handle_reminder_delete,
19
- handle_followup_create, handle_followup_update,
20
- handle_followup_complete, handle_followup_delete,
21
- )
22
- from tools_learnings import (
23
- handle_learning_add, handle_learning_search,
24
- handle_learning_update, handle_learning_delete, handle_learning_list,
25
- )
26
- from tools_credentials import (
27
- handle_credential_get, handle_credential_create,
28
- handle_credential_update, handle_credential_delete, handle_credential_list,
29
- )
30
- from tools_task_history import (
31
- handle_task_log, handle_task_list, handle_task_frequency,
32
- )
33
- from plugin_loader import load_all_plugins, load_plugin, remove_plugin, list_plugins
34
-
35
-
36
- # ── Graceful shutdown: close DB on any termination signal ──────────
37
- def _shutdown_handler(signum, frame):
38
- close_db()
39
- sys.exit(0)
40
-
41
-
42
- def _server_init():
43
- """Run all side effects: signals, PID, DB, auto-update, plugins.
44
-
45
- Called only when the server is actually started (not on import).
46
- """
47
- signal.signal(signal.SIGTERM, _shutdown_handler)
48
- signal.signal(signal.SIGINT, _shutdown_handler)
49
-
50
- # ── Write PID file for stale process detection ─────────────────
51
- _data_dir = os.path.join(os.environ.get("NEXO_HOME", os.path.join(os.path.expanduser("~"), ".nexo")), "data")
52
- os.makedirs(_data_dir, exist_ok=True)
53
- _pid_file = os.path.join(_data_dir, "nexo.pid")
54
- with open(_pid_file, "w") as f:
55
- f.write(str(os.getpid()))
56
-
57
- init_db()
58
-
59
- # ── Auto-update check (non-blocking, max 5s) ──────────────────
60
- try:
61
- from auto_update import auto_update_check
62
- import threading
63
-
64
- def _bg_update():
65
- try:
66
- result = auto_update_check()
67
- if result.get("git_update"):
68
- print(f"[NEXO] {result['git_update']}", file=sys.stderr)
69
- if result.get("npm_notice"):
70
- print(f"[NEXO] {result['npm_notice']}", file=sys.stderr)
71
- if result.get("claude_md_update"):
72
- print(f"[NEXO] {result['claude_md_update']}", file=sys.stderr)
73
- for m in result.get("migrations", []):
74
- if m["status"] == "failed":
75
- print(f"[NEXO] Migration {m['file']} FAILED: {m['message']}", file=sys.stderr)
76
- except Exception as e:
77
- print(f"[NEXO auto-update] error: {e}", file=sys.stderr)
78
-
79
- _update_thread = threading.Thread(target=_bg_update, daemon=True)
80
- _update_thread.start()
81
- _update_thread.join(timeout=5) # Wait at most 5 seconds
82
- except Exception:
83
- pass # Never break startup
84
-
85
- # ── Load plugins ───────────────────────────────────────────────
86
- load_all_plugins(mcp)
87
-
88
-
89
- mcp = FastMCP(
90
- name="nexo",
91
- instructions=(
92
- "NEXO — cognitive co-operator. Save important info from tool results before they clear.\n\n"
93
- "## Rules\n"
94
- "- **Heartbeat:** `nexo_heartbeat(sid=SID, task='...', context_hint='...')` every user msg. "
95
- "React: DIARY REMINDER→write diary, VIBE:NEGATIVE→ultra-concise, AUTO-PRIME→read learnings\n"
96
- "- **Guard:** `nexo_guard_check(files='...', area='...')` BEFORE editing code. "
97
- "Blocking rules→resolve first. `nexo_track(sid=SID, paths=[...])` before shared files\n"
98
- "- **Followups:** NEXO tasks, execute silently. 'done'/'all set'→`nexo_followup_complete` NOW. "
99
- "Reminders=user's, alert when due\n"
100
- "- **Observe:** correction→learning+trust. 'tomorrow'→followup. person→entity. open topic→followup 3d\n"
101
- "- **Delegate:** prefer direct. If needed: `nexo_context_packet(area)` + guard + 'if unsure STOP'\n"
102
- "- **Memory:** `nexo_recall` searches all. Capture: errors→`nexo_learning_add`, prefs, entities, decisions\n"
103
- "- **Change log:** `nexo_change_log(...)` after production edits. NOT for config dir\n"
104
- "- **Diary:** `nexo_session_diary_write(...)` mandatory on close. Include self_critique\n"
105
- "- **Cortex:** `nexo_cortex_check` before budget/campaign/architecture changes\n"
106
- "- **Dissonance:** user contradicts memory→`nexo_cognitive_dissonance`. Frustrated→force=True\n"
107
- "- **Trust:** <40=paranoid verify twice, >80=fluid. Check: `nexo_cognitive_trust`"
108
- ),
109
- )
110
-
111
-
112
- # ── Session management (3 tools) ──────────────────────────────────
113
-
114
- @mcp.tool
115
- def nexo_startup(task: str = "Startup", claude_session_id: str = "") -> str:
116
- """Register new session, clean stale ones, return active sessions + alerts.
117
-
118
- Call this ONCE at the start of every conversation.
119
- Returns the session ID (SID) — store it for use in all other nexo_ tools.
120
-
121
- Args:
122
- task: Initial task description.
123
- claude_session_id: The Claude Code session UUID (from session-briefing or hook).
124
- Pass this to enable automatic inter-terminal inbox detection.
125
- """
126
- return handle_startup(task, claude_session_id=claude_session_id)
127
-
128
-
129
- @mcp.tool
130
- def nexo_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
131
- """Update session task, check inbox and pending questions. Auto-detects trust events.
132
-
133
- Call this at the START of every user interaction (before doing work).
134
- Args:
135
- sid: Your session ID from nexo_startup.
136
- task: Brief description of current work (5-10 words).
137
- context_hint: Last 2-3 sentences from the user or current topic. Used for sentiment detection, trust auto-scoring, and mid-session RAG. ALWAYS provide this for best results.
138
- """
139
- return handle_heartbeat(sid, task, context_hint)
140
-
141
-
142
- @mcp.tool
143
- def nexo_stop(sid: str) -> str:
144
- """Cleanly close a session. Removes it from active sessions immediately.
145
-
146
- Call this when ending a conversation to avoid ghost sessions.
147
- Args:
148
- sid: Session ID to close."""
149
- from tools_sessions import handle_stop
150
- return handle_stop(sid)
151
-
152
- @mcp.tool
153
- def nexo_status(keyword: str = "") -> str:
154
- """List active sessions. Filter by keyword if provided."""
155
- return handle_status(keyword if keyword else None)
156
-
157
-
158
- @mcp.tool
159
- def nexo_context_packet(area: str, files: str = "") -> str:
160
- """Build a context packet for subagent injection. Returns learnings + changes + followups + preferences + cognitive memories for a specific area.
161
-
162
- MUST call before delegating ANY task to a subagent. Inject the result into the subagent's prompt.
163
-
164
- Args:
165
- area: Project/area name (e.g., 'ecommerce', 'shopify', 'backend', 'mobile-app', 'nexo', 'infrastructure').
166
- files: Optional comma-separated file paths for additional context.
167
- """
168
- return handle_context_packet(area, files)
169
-
170
-
171
- @mcp.tool
172
- def nexo_smart_startup() -> str:
173
- """Pre-load relevant cognitive memories based on pending followups, due reminders, and last session topics.
174
-
175
- Call during startup (after nexo_startup) to ensure the session starts with the right context loaded.
176
- Returns up to 10 memories matching the current operational state.
177
- """
178
- return handle_smart_startup_query()
179
-
180
-
181
- # ── Session Checkpoints (auto-compaction continuity) ──────────────
182
-
183
- @mcp.tool
184
- def nexo_checkpoint_save(
185
- sid: str,
186
- task: str = '',
187
- task_status: str = 'active',
188
- active_files: str = '[]',
189
- current_goal: str = '',
190
- decisions_summary: str = '',
191
- errors_found: str = '',
192
- reasoning_thread: str = '',
193
- next_step: str = ''
194
- ) -> str:
195
- """Save a session checkpoint for auto-compaction continuity.
196
-
197
- Call this BEFORE context compaction to preserve session state.
198
- The PostCompact hook reads this checkpoint and re-injects it as a
199
- Core Memory Block, so the session continues seamlessly.
200
-
201
- Args:
202
- sid: Session ID.
203
- task: Current task description.
204
- task_status: One of 'active', 'investigating', 'fixing', 'deploying', 'blocked'.
205
- active_files: JSON array of file paths currently being worked on.
206
- current_goal: What you're trying to achieve right now (1-2 sentences).
207
- decisions_summary: Recent decisions with brief reasoning (2-3 lines).
208
- errors_found: Errors encountered and their status (resolved/open).
209
- reasoning_thread: Your current chain of thought (1-2 sentences).
210
- next_step: The concrete next action to take.
211
- """
212
- from db import save_checkpoint
213
- result = save_checkpoint(
214
- sid=sid, task=task, task_status=task_status,
215
- active_files=active_files, current_goal=current_goal,
216
- decisions_summary=decisions_summary, errors_found=errors_found,
217
- reasoning_thread=reasoning_thread, next_step=next_step,
218
- )
219
- return f"Checkpoint saved for {sid}. Compaction #{result['compaction_count']}. PostCompact will re-inject this as Core Memory Block."
220
-
221
-
222
- @mcp.tool
223
- def nexo_checkpoint_read(sid: str = '') -> str:
224
- """Read the latest session checkpoint. Used by PostCompact hook and for manual recovery.
225
-
226
- Args:
227
- sid: Session ID. If empty, returns the most recent checkpoint from any session.
228
- """
229
- from db import read_checkpoint
230
- cp = read_checkpoint(sid)
231
- if not cp:
232
- return "No checkpoint found."
233
-
234
- lines = [f"CHECKPOINT for {cp['sid']} (compaction #{cp['compaction_count']})"]
235
- lines.append(f"Task: {cp['task']} ({cp['task_status']})")
236
- if cp.get('current_goal'):
237
- lines.append(f"Goal: {cp['current_goal']}")
238
- if cp.get('active_files') and cp['active_files'] != '[]':
239
- lines.append(f"Files: {cp['active_files']}")
240
- if cp.get('decisions_summary'):
241
- lines.append(f"Decisions: {cp['decisions_summary']}")
242
- if cp.get('errors_found'):
243
- lines.append(f"Errors: {cp['errors_found']}")
244
- if cp.get('reasoning_thread'):
245
- lines.append(f"Context: {cp['reasoning_thread']}")
246
- if cp.get('next_step'):
247
- lines.append(f"Next: {cp['next_step']}")
248
- lines.append(f"Updated: {cp['updated_at']}")
249
- return "\n".join(lines)
250
-
251
-
252
- # ── File coordination (3 tools) ───────────────────────────────────
253
-
254
- @mcp.tool
255
- def nexo_track(sid: str, paths: list[str]) -> str:
256
- """Track files being edited. Detects conflicts with other sessions.
257
-
258
- MUST call before editing any shared file.
259
- Args:
260
- sid: Your session ID.
261
- paths: List of absolute file paths to track.
262
- """
263
- return handle_track(sid, paths)
264
-
265
-
266
- @mcp.tool
267
- def nexo_untrack(sid: str, paths: list[str] | None = None) -> str:
268
- """Stop tracking files. If no paths given, releases all.
269
-
270
- Args:
271
- sid: Your session ID.
272
- paths: File paths to release. Omit to release all.
273
- """
274
- return handle_untrack(sid, paths)
275
-
276
-
277
- @mcp.tool
278
- def nexo_files() -> str:
279
- """Show all tracked files across all active sessions with conflict detection."""
280
- return handle_files()
281
-
282
-
283
- # ── Messaging (4 tools) ───────────────────────────────────────────
284
-
285
- @mcp.tool
286
- def nexo_send(from_sid: str, to_sid: str, text: str) -> str:
287
- """Send a fire-and-forget message to another session or broadcast.
288
-
289
- Args:
290
- from_sid: Your session ID.
291
- to_sid: Target session ID, or 'all' for broadcast.
292
- text: Message content.
293
- """
294
- return handle_send(from_sid, to_sid, text)
295
-
296
-
297
- @mcp.tool
298
- def nexo_ask(from_sid: str, to_sid: str, question: str) -> str:
299
- """Ask a question to another session (they see it on next heartbeat).
300
-
301
- Args:
302
- from_sid: Your session ID.
303
- to_sid: Target session ID.
304
- question: The question text.
305
- Returns: Question ID (qid) for checking the answer later.
306
- """
307
- return handle_ask(from_sid, to_sid, question)
308
-
309
-
310
- @mcp.tool
311
- def nexo_answer(qid: str, answer: str) -> str:
312
- """Answer a pending question from another session.
313
-
314
- Args:
315
- qid: The question ID shown in heartbeat output.
316
- answer: Your response.
317
- """
318
- return handle_answer(qid, answer)
319
-
320
-
321
- @mcp.tool
322
- def nexo_check_answer(qid: str) -> str:
323
- """Check if a question has been answered.
324
-
325
- Args:
326
- qid: The question ID from nexo_ask.
327
- """
328
- return handle_check_answer(qid)
329
-
330
-
331
- # ── Operations: Reminders + Menu (2 tools, read-only) ─────────────
332
-
333
- @mcp.tool
334
- def nexo_reminders(filter: str = "due") -> str:
335
- """Check reminders and followups.
336
-
337
- Args:
338
- filter: 'due' (vencidos/hoy), 'all' (todos activos), 'followups' (solo NEXO followups)
339
- """
340
- return handle_reminders(filter)
341
-
342
-
343
- @mcp.tool
344
- def nexo_menu() -> str:
345
- """Generate the NEXO operations center menu with alerts and active sessions.
346
-
347
- Shows: date, due alerts, all menu items by category, active sessions.
348
- Uses box-drawing characters for formatting.
349
- """
350
- return handle_menu()
351
-
352
-
353
- # ── Reminders CRUD (4 tools) ──────────────────────────────────────
354
-
355
- @mcp.tool
356
- def nexo_reminder_create(id: str, description: str, date: str = "", category: str = "general") -> str:
357
- """Create a new reminder for the user.
358
-
359
- Args:
360
- id: Unique ID starting with 'R' (e.g., R90).
361
- description: What needs to be done.
362
- date: Target date YYYY-MM-DD (optional).
363
- category: One of: decisions, tasks, waiting, ideas, general.
364
- """
365
- return handle_reminder_create(id, description, date, category)
366
-
367
-
368
- @mcp.tool
369
- def nexo_reminder_update(id: str, description: str = "", date: str = "", status: str = "", category: str = "") -> str:
370
- """Update fields of an existing reminder. Only non-empty fields are changed.
371
-
372
- Args:
373
- id: Reminder ID (e.g., R87).
374
- description: New description (optional).
375
- date: New date YYYY-MM-DD (optional).
376
- status: New status (optional).
377
- category: New category (optional).
378
- """
379
- return handle_reminder_update(id, description, date, status, category)
380
-
381
-
382
- @mcp.tool
383
- def nexo_reminder_complete(id: str) -> str:
384
- """Mark a reminder as completed with today's date.
385
-
386
- Args:
387
- id: Reminder ID (e.g., R87).
388
- """
389
- return handle_reminder_complete(id)
390
-
391
-
392
- @mcp.tool
393
- def nexo_reminder_delete(id: str) -> str:
394
- """Delete a reminder permanently.
395
-
396
- Args:
397
- id: Reminder ID (e.g., R87).
398
- """
399
- return handle_reminder_delete(id)
400
-
401
-
402
- # ── Followups CRUD (4 tools) ──────────────────────────────────────
403
-
404
- @mcp.tool
405
- def nexo_followup_create(id: str, description: str, date: str = "", verification: str = "", reasoning: str = "", recurrence: str = "", priority: str = "medium") -> str:
406
- """Create a new NEXO followup (autonomous task).
407
-
408
- Args:
409
- id: Unique ID starting with 'NF' (e.g., NF-MCP2).
410
- description: What to verify/do.
411
- date: Target date YYYY-MM-DD (optional).
412
- verification: How to verify completion (optional).
413
- reasoning: WHY this followup exists — what decision/context led to it (optional).
414
- recurrence: Auto-regenerate pattern (optional). Formats: 'weekly:monday', 'monthly:1', 'monthly:15', 'quarterly'.
415
- When completed, a new followup is auto-created with the next date. The completed one is archived with date suffix.
416
- priority: critical, high, medium, low (default: medium).
417
- """
418
- result = handle_followup_create(id, description, date, verification, reasoning, recurrence)
419
- if priority in ('critical', 'high', 'low') and 'created' in result:
420
- from db import get_db
421
- get_db().execute("UPDATE followups SET priority = ? WHERE id = ?", (priority, id))
422
- get_db().commit()
423
- return result
424
-
425
-
426
- @mcp.tool
427
- def nexo_followup_update(id: str, description: str = "", date: str = "", verification: str = "", status: str = "", priority: str = "") -> str:
428
- """Update fields of an existing followup. Only non-empty fields are changed.
429
-
430
- Args:
431
- id: Followup ID (e.g., NF45).
432
- description: New description (optional).
433
- date: New date YYYY-MM-DD (optional).
434
- verification: New verification text (optional).
435
- status: New status (optional).
436
- priority: critical, high, medium, low (optional).
437
- """
438
- result = handle_followup_update(id, description, date, verification, status)
439
- if priority in ('critical', 'high', 'medium', 'low'):
440
- from db import get_db
441
- get_db().execute("UPDATE followups SET priority = ? WHERE id = ?", (priority, id))
442
- get_db().commit()
443
- return result
444
-
445
-
446
- @mcp.tool
447
- def nexo_followup_complete(id: str, result: str = "") -> str:
448
- """Mark a followup as completed. Appends result to verification field.
449
-
450
- Args:
451
- id: Followup ID (e.g., NF45).
452
- result: What was found/done (optional).
453
- """
454
- return handle_followup_complete(id, result)
455
-
456
-
457
- @mcp.tool
458
- def nexo_followup_delete(id: str) -> str:
459
- """Delete a followup permanently.
460
-
461
- Args:
462
- id: Followup ID (e.g., NF45).
463
- """
464
- return handle_followup_delete(id)
465
-
466
-
467
- # ── Learnings CRUD (5 tools) ──────────────────────────────────────
468
-
469
- @mcp.tool
470
- def nexo_learning_add(category: str, title: str, content: str, reasoning: str = "", priority: str = "medium") -> str:
471
- """Add a new learning (resolved error, pattern, gotcha).
472
-
473
- Args:
474
- category: Free-form category name (e.g., 'backend', 'frontend', 'devops', 'infrastructure', 'security'). Use consistent names across learnings.
475
- title: Short title for the learning.
476
- content: Full description with context and solution.
477
- reasoning: WHY this matters — what led to discovering this (optional).
478
- priority: critical, high, medium, low (default: medium). Critical/high never decay below floor.
479
- """
480
- return handle_learning_add(category, title, content, reasoning, priority=priority)
481
-
482
-
483
- @mcp.tool
484
- def nexo_learning_search(query: str, category: str = "") -> str:
485
- """Search learnings by keyword. Searches title and content.
486
-
487
- Args:
488
- query: Search term.
489
- category: Filter by category (optional).
490
- """
491
- return handle_learning_search(query, category)
492
-
493
-
494
- @mcp.tool
495
- def nexo_learning_update(id: int, title: str = "", content: str = "", category: str = "", priority: str = "") -> str:
496
- """Update a learning entry. Only non-empty fields are changed.
497
-
498
- Args:
499
- id: Learning ID number.
500
- title: New title (optional).
501
- content: New content (optional).
502
- category: New category (optional).
503
- priority: critical, high, medium, low (optional).
504
- """
505
- return handle_learning_update(id, title, content, category, priority=priority)
506
-
507
-
508
- @mcp.tool
509
- def nexo_learning_delete(id: int) -> str:
510
- """Delete a learning entry.
511
-
512
- Args:
513
- id: Learning ID number.
514
- """
515
- return handle_learning_delete(id)
516
-
517
-
518
- @mcp.tool
519
- def nexo_learning_list(category: str = "") -> str:
520
- """List all learnings, grouped by category.
521
-
522
- Args:
523
- category: Filter by category (optional). If empty, shows all grouped.
524
- """
525
- return handle_learning_list(category)
526
-
527
-
528
- # ── Search index ──────────────────────────────────────────────────
529
-
530
- @mcp.tool
531
- def nexo_reindex() -> str:
532
- """Force full rebuild of the FTS5 search index. Use after bulk changes or if search seems stale."""
533
- conn = get_db()
534
- rebuild_fts_index(conn)
535
- count = conn.execute("SELECT COUNT(*) FROM unified_search").fetchone()[0]
536
- sources = conn.execute("SELECT source, COUNT(*) as cnt FROM unified_search GROUP BY source ORDER BY cnt DESC").fetchall()
537
- lines = [f"Index rebuilt: {count} documentos"]
538
- for s in sources:
539
- lines.append(f" {s[0]:12s} → {s[1]}")
540
- return "\n".join(lines)
541
-
542
-
543
- @mcp.tool
544
- def nexo_index_add_dir(path: str, dir_type: str = "code",
545
- patterns: str = "*.php,*.js,*.json,*.py,*.ts,*.tsx",
546
- notes: str = "") -> str:
547
- """Register a new directory for FTS5 search indexing. Survives restarts.
548
-
549
- Args:
550
- path: Absolute path to directory (supports ~).
551
- dir_type: 'code' for source files, 'md' for markdown docs.
552
- patterns: Comma-separated glob patterns (only for code type).
553
- notes: Description of what this directory contains.
554
- """
555
- result = fts_add_dir(path, dir_type, patterns, notes)
556
- if "error" in result:
557
- return f"ERROR: {result['error']}"
558
- return f"Directory registered: {result['path']} ({result['dir_type']}, patterns: {result['patterns']})\nUse nexo_reindex to index now."
559
-
560
-
561
- @mcp.tool
562
- def nexo_index_remove_dir(path: str) -> str:
563
- """Remove a directory from FTS5 indexing and clean up its entries.
564
-
565
- Args:
566
- path: Path to directory to remove.
567
- """
568
- result = fts_remove_dir(path)
569
- if "error" in result:
570
- return f"ERROR: {result['error']}"
571
- return f"Directory removed from index: {result['removed']}"
572
-
573
-
574
- @mcp.tool
575
- def nexo_index_dirs() -> str:
576
- """List all directories being indexed by FTS5 (builtin + dynamic)."""
577
- dirs = fts_list_dirs()
578
- if not dirs:
579
- return "No directories configured."
580
- lines = ["INDEXED DIRECTORIES:"]
581
- for d in dirs:
582
- source_tag = "⚙️" if d["source"] == "builtin" else "➕"
583
- notes = f" — {d['notes']}" if d.get("notes") else ""
584
- lines.append(f" {source_tag} [{d['type']}] {d['path']}")
585
- lines.append(f" patterns: {d['patterns']}{notes}")
586
- return "\n".join(lines)
587
-
588
-
589
- # ── Credentials CRUD (5 tools) ────────────────────────────────────
590
-
591
- @mcp.tool
592
- def nexo_credential_get(service: str, key: str = "") -> str:
593
- """Get credential value(s) for a service.
594
-
595
- Args:
596
- service: Service name (e.g., google-ads, meta-ads, shopify).
597
- key: Specific key (optional). If empty, returns all for the service.
598
- """
599
- return handle_credential_get(service, key)
600
-
601
-
602
- @mcp.tool
603
- def nexo_credential_create(service: str, key: str, value: str, notes: str = "") -> str:
604
- """Store a new credential.
605
-
606
- Args:
607
- service: Service name (e.g., google-ads, cloudflare).
608
- key: Key name (e.g., api_key, token, ssh).
609
- value: The secret value.
610
- notes: Description or context (optional).
611
- """
612
- return handle_credential_create(service, key, value, notes)
613
-
614
-
615
- @mcp.tool
616
- def nexo_credential_update(service: str, key: str, value: str = "", notes: str = "") -> str:
617
- """Update a credential's value and/or notes.
618
-
619
- Args:
620
- service: Service name.
621
- key: Key name.
622
- value: New value (optional).
623
- notes: New notes (optional).
624
- """
625
- return handle_credential_update(service, key, value, notes)
626
-
627
-
628
- @mcp.tool
629
- def nexo_credential_delete(service: str, key: str = "") -> str:
630
- """Delete credential(s). If no key, deletes all for the service.
631
-
632
- Args:
633
- service: Service name.
634
- key: Specific key (optional). If empty, deletes ALL for service.
635
- """
636
- return handle_credential_delete(service, key)
637
-
638
-
639
- @mcp.tool
640
- def nexo_credential_list(service: str = "") -> str:
641
- """List credentials (names and notes only, no values).
642
-
643
- Args:
644
- service: Filter by service (optional). If empty, shows all.
645
- """
646
- return handle_credential_list(service)
647
-
648
-
649
- # ── Task History (3 tools) ────────────────────────────────────────
650
-
651
- @mcp.tool
652
- def nexo_task_log(task_num: str, task_name: str, notes: str = "", reasoning: str = "") -> str:
653
- """Record that an operational task was executed.
654
-
655
- Args:
656
- task_num: Task number from the checklist (e.g., '7', '7b').
657
- task_name: Task name (e.g., 'Google Ads').
658
- notes: Execution summary (optional).
659
- reasoning: WHY this task was executed now — what triggered it (optional).
660
- """
661
- return handle_task_log(task_num, task_name, notes, reasoning)
662
-
663
-
664
- @mcp.tool
665
- def nexo_task_list(task_num: str = "", days: int = 30) -> str:
666
- """Show execution history for operational tasks.
667
-
668
- Args:
669
- task_num: Filter by task number (optional).
670
- days: How many days back to show (default 30).
671
- """
672
- return handle_task_list(task_num, days)
673
-
674
-
675
- @mcp.tool
676
- def nexo_task_frequency() -> str:
677
- """Check which operational tasks are overdue based on their frequency.
678
-
679
- Compares last execution date vs configured frequency.
680
- Returns overdue tasks or 'all tasks up to date'.
681
- """
682
- return handle_task_frequency()
683
-
684
-
685
- # ── Plugin Management (3 tools) ─────────────────────────────────
686
-
687
- @mcp.tool
688
- def nexo_plugin_load(filename: str) -> str:
689
- """Load or reload a plugin. Searches repo plugins/ first, then NEXO_HOME/plugins/.
690
-
691
- Args:
692
- filename: Plugin filename (e.g., 'entities.py').
693
- """
694
- try:
695
- n = load_plugin(mcp, filename)
696
- return f"Plugin {filename}: {n} tools registered."
697
- except Exception as e:
698
- return f"Error loading plugin {filename}: {e}"
699
-
700
-
701
- @mcp.tool
702
- def nexo_plugin_list() -> str:
703
- """List all loaded plugins and their tools, showing source (repo/personal)."""
704
- plugins = list_plugins()
705
- if not plugins:
706
- return "No plugins loaded."
707
- lines = ["LOADED PLUGINS:"]
708
- for p in plugins:
709
- names = p["tool_names"] or "(no tools)"
710
- source = p.get("source", "repo")
711
- lines.append(f" [{source}] {p['filename']} — {p['tools_count']} tools: {names}")
712
- return "\n".join(lines)
713
-
714
-
715
- @mcp.tool
716
- def nexo_plugin_remove(filename: str) -> str:
717
- """Unregister a plugin's tools from MCP (does not delete files).
718
-
719
- Args:
720
- filename: Plugin filename (e.g., 'entities.py').
721
- """
722
- try:
723
- removed = remove_plugin(mcp, filename)
724
- if removed:
725
- return f"Plugin {filename} unregistered. Tools removed: {', '.join(removed)}"
726
- return f"Plugin {filename} unregistered (had no registered tools)."
727
- except Exception as e:
728
- return f"Error removing plugin {filename}: {e}"
729
-
730
-
731
- if __name__ == "__main__":
732
- _server_init()
733
- mcp.run(transport="stdio")