nexo-brain 1.6.0 → 2.0.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.
- package/README.md +102 -79
- package/bin/nexo-brain.js +681 -303
- package/bin/postinstall.js +46 -0
- package/package.json +14 -2
- package/scripts/migrate-to-unified.sh +813 -0
- package/scripts/migrate-v1.7-to-v1.8.py +214 -0
- package/scripts/pre-commit-check.sh +1 -1
- package/src/__pycache__/auto_close_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-314.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-310.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-310.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-310.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/server.cpython-310.pyc +0 -0
- package/src/__pycache__/server.cpython-314.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-310.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
- package/src/auto_close_sessions.py +4 -3
- package/src/auto_update.py +634 -0
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/cognitive/_core.py +7 -3
- package/src/cognitive/_decay.py +1 -1
- package/src/cognitive/_memory.py +7 -3
- package/src/cognitive/_search.py +12 -10
- package/src/cognitive/_trust.py +3 -3
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
- package/src/dashboard/app.py +9 -3
- package/src/dashboard/templates/dashboard.html +4 -4
- package/src/dashboard/templates/operations.html +6 -6
- package/src/db/__init__.py +2 -1
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
- package/src/db/_core.py +7 -3
- package/src/db/_episodic.py +69 -1
- package/src/db/_fts.py +12 -12
- package/src/db/_reminders.py +89 -15
- package/src/db/_schema.py +41 -0
- package/src/evolution_cycle.py +33 -11
- package/src/hooks/__pycache__/auto_capture.cpython-310.pyc +0 -0
- package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
- package/src/hooks/auto_capture.py +1 -1
- package/src/hooks/capture-tool-logs.sh +76 -0
- package/src/hooks/inbox-hook.sh +2 -1
- package/src/hooks/post-compact.sh +2 -1
- package/src/hooks/pre-compact.sh +104 -2
- package/src/hooks/session-start.sh +6 -2
- package/src/hooks/session-stop.sh +2 -1
- package/src/kg_populate.py +4 -1
- package/src/migrate_embeddings.py +4 -1
- package/src/plugin_loader.py +100 -34
- package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/agents.py +2 -2
- package/src/plugins/backup.py +5 -4
- package/src/plugins/cognitive_memory.py +1 -1
- package/src/plugins/core_rules.py +5 -1
- package/src/plugins/episodic_memory.py +43 -16
- package/src/plugins/evolution.py +7 -2
- package/src/plugins/guard.py +45 -17
- package/src/plugins/update.py +238 -0
- package/src/requirements.txt +12 -0
- package/src/rules/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-310.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/check-context.py +13 -3
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/analyze_session.py +3 -1
- package/src/scripts/deep-sleep/apply_findings.py +7 -4
- package/src/scripts/deep-sleep/collect_transcripts.py +3 -1
- package/src/scripts/nexo-auto-update.py +4 -211
- package/src/scripts/nexo-backup.sh +25 -0
- package/src/scripts/nexo-brain-activation.sh +26 -26
- package/src/scripts/nexo-catchup.py +39 -25
- package/src/scripts/nexo-cognitive-decay.py +12 -5
- package/src/scripts/nexo-daily-self-audit.py +36 -14
- package/src/scripts/nexo-deep-sleep.sh +4 -3
- package/src/scripts/nexo-evolution-run.py +40 -12
- package/src/scripts/nexo-followup-hygiene.py +6 -3
- package/src/scripts/nexo-github-monitor.py +11 -4
- package/src/scripts/nexo-immune.py +20 -3
- package/src/scripts/nexo-inbox-hook.sh +2 -1
- package/src/scripts/nexo-install.py +6 -0
- package/src/scripts/nexo-learning-housekeep.py +245 -0
- package/src/scripts/nexo-learning-validator.py +12 -2
- package/src/scripts/nexo-migrate.py +232 -0
- package/src/scripts/nexo-postmortem-consolidator.py +38 -19
- package/src/scripts/nexo-pre-commit.py +3 -1
- package/src/scripts/nexo-prevent-sleep.sh +29 -0
- package/src/scripts/nexo-proactive-dashboard.py +8 -6
- package/src/scripts/nexo-runtime-preflight.py +59 -55
- package/src/scripts/nexo-send-email.py +2 -2
- package/src/scripts/nexo-send-reply.py +3 -1
- package/src/scripts/nexo-sleep.py +21 -5
- package/src/scripts/nexo-snapshot-restore.sh +2 -1
- package/src/scripts/nexo-synthesis.py +19 -4
- package/src/scripts/nexo-tcc-approve.sh +79 -0
- package/src/scripts/nexo-update.sh +161 -0
- package/src/scripts/nexo-watchdog-smoke.py +18 -13
- package/src/scripts/nexo-watchdog.sh +41 -31
- package/src/server.py +107 -44
- package/src/storage_router.py +6 -2
- package/src/tools_coordination.py +14 -14
- package/src/tools_credentials.py +11 -11
- package/src/tools_learnings.py +36 -27
- package/src/tools_menu.py +7 -6
- package/src/tools_reminders.py +11 -5
- package/src/tools_reminders_crud.py +11 -9
- package/src/tools_sessions.py +62 -187
- package/src/tools_task_history.py +7 -7
- package/templates/CLAUDE.md.template +49 -469
- package/templates/launchagents/README.md +7 -7
- package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
- package/templates/launchagents/com.nexo.catchup.plist +4 -0
- package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
- package/templates/launchagents/com.nexo.dashboard.plist +5 -1
- package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
- package/templates/launchagents/com.nexo.evolution.plist +4 -0
- package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
- package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
- package/templates/launchagents/com.nexo.immune.plist +4 -0
- package/templates/launchagents/com.nexo.postmortem.plist +4 -0
- package/templates/launchagents/com.nexo.self-audit.plist +4 -0
- package/templates/launchagents/com.nexo.synthesis.plist +4 -0
- package/templates/launchagents/com.nexo.watchdog.plist +4 -0
- package/templates/openclaw.json +1 -1
- package/tests/__pycache__/__init__.cpython-310.pyc +0 -0
- package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/conftest.py +2 -2
- package/tests/test_cognitive.py +7 -6
- package/tests/test_migrations.py +29 -3
package/bin/nexo-brain.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* nexo-brain — Interactive installer for NEXO cognitive co-operator.
|
|
4
4
|
*
|
|
5
|
-
* Usage: npx
|
|
5
|
+
* Usage: npx nexo-brain
|
|
6
6
|
*
|
|
7
7
|
* What it does:
|
|
8
8
|
* 1. Asks for the co-operator's name
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 3. Installs Python dependencies (fastembed, numpy, mcp)
|
|
11
11
|
* 4. Creates ~/.nexo/ with DB, personality, and config
|
|
12
12
|
* 5. Configures Claude Code MCP settings
|
|
13
|
-
* 6. Creates LaunchAgents for
|
|
13
|
+
* 6. Creates LaunchAgents (macOS) / systemd timers (Linux) / crontab (fallback) for automated processes
|
|
14
14
|
* 7. Generates CLAUDE.md with the operator's instructions
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ const fs = require("fs");
|
|
|
19
19
|
const path = require("path");
|
|
20
20
|
const readline = require("readline");
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
let NEXO_HOME = path.join(require("os").homedir(), ".nexo");
|
|
23
23
|
const CLAUDE_SETTINGS = path.join(
|
|
24
24
|
require("os").homedir(),
|
|
25
25
|
".claude",
|
|
@@ -52,6 +52,456 @@ function log(msg) {
|
|
|
52
52
|
console.log(` ${msg}`);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
56
|
+
// CORE PROCESS & HOOK DEFINITIONS
|
|
57
|
+
// All 13 nightly/periodic processes and all 7 core hooks that make NEXO functional.
|
|
58
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Complete definition of all 13 NEXO automated processes.
|
|
62
|
+
* Each entry specifies the script, its interpreter ("python" or "bash"),
|
|
63
|
+
* the schedule type, and default schedule values.
|
|
64
|
+
*/
|
|
65
|
+
const ALL_PROCESSES = [
|
|
66
|
+
// --- Every 5 minutes ---
|
|
67
|
+
{ name: "auto-close-sessions", script: "auto_close_sessions.py", interpreter: "python", scriptDir: "root",
|
|
68
|
+
type: "interval", intervalMinutes: 5, purpose: "Clean stale sessions" },
|
|
69
|
+
{ name: "watchdog", script: "nexo-watchdog.sh", interpreter: "bash", scriptDir: "scripts",
|
|
70
|
+
type: "interval", intervalMinutes: 5, purpose: "Health monitoring" },
|
|
71
|
+
// --- Every 30 minutes ---
|
|
72
|
+
{ name: "immune", script: "nexo-immune.py", interpreter: "python", scriptDir: "scripts",
|
|
73
|
+
type: "interval", intervalMinutes: 30, purpose: "System immunity checks" },
|
|
74
|
+
// --- Every 2 hours ---
|
|
75
|
+
{ name: "synthesis", script: "nexo-synthesis.py", interpreter: "python", scriptDir: "scripts",
|
|
76
|
+
type: "interval", intervalMinutes: 120, purpose: "Memory synthesis" },
|
|
77
|
+
// --- Every hour ---
|
|
78
|
+
{ name: "backup", script: "nexo-backup.sh", interpreter: "bash", scriptDir: "scripts",
|
|
79
|
+
type: "interval", intervalMinutes: 60, purpose: "DB backups" },
|
|
80
|
+
// --- RunAtLoad (once on boot) ---
|
|
81
|
+
{ name: "catchup", script: "nexo-catchup.py", interpreter: "python", scriptDir: "scripts",
|
|
82
|
+
type: "runAtLoad", purpose: "Session catchup" },
|
|
83
|
+
{ name: "tcc-approve", script: "nexo-tcc-approve.sh", interpreter: "bash", scriptDir: "scripts",
|
|
84
|
+
type: "runAtLoad", macOnly: true, watchPaths: ["~/.local/share/claude/versions"],
|
|
85
|
+
purpose: "Auto-approve macOS permissions for Claude updates" },
|
|
86
|
+
// --- KeepAlive (persistent daemon) ---
|
|
87
|
+
{ name: "prevent-sleep", script: "nexo-prevent-sleep.sh", interpreter: "bash", scriptDir: "scripts",
|
|
88
|
+
type: "keepAlive", purpose: "Keep machine awake for nocturnal processes" },
|
|
89
|
+
// --- Daily (times from schedule.json) ---
|
|
90
|
+
{ name: "cognitive-decay", script: "nexo-cognitive-decay.py", interpreter: "python", scriptDir: "scripts",
|
|
91
|
+
type: "daily", defaultHour: 3, defaultMinute: 0, purpose: "Memory decay" },
|
|
92
|
+
{ name: "postmortem", script: "nexo-postmortem-consolidator.py", interpreter: "python", scriptDir: "scripts",
|
|
93
|
+
type: "daily", defaultHour: 23, defaultMinute: 30, purpose: "Session consolidation" },
|
|
94
|
+
{ name: "self-audit", script: "nexo-daily-self-audit.py", interpreter: "python", scriptDir: "scripts",
|
|
95
|
+
type: "daily", defaultHour: 7, defaultMinute: 0, purpose: "Self-diagnostic" },
|
|
96
|
+
{ name: "sleep", script: "nexo-sleep.py", interpreter: "python", scriptDir: "scripts",
|
|
97
|
+
type: "daily", defaultHour: 4, defaultMinute: 0, purpose: "Sleep cycle" },
|
|
98
|
+
{ name: "deep-sleep", script: "nexo-deep-sleep.sh", interpreter: "bash", scriptDir: "scripts",
|
|
99
|
+
type: "daily", defaultHour: 4, defaultMinute: 30, purpose: "Deep sleep analysis" },
|
|
100
|
+
// --- Weekly (day + time from schedule.json) ---
|
|
101
|
+
{ name: "evolution", script: "nexo-evolution-run.py", interpreter: "python", scriptDir: "scripts",
|
|
102
|
+
type: "weekly", defaultDay: "sunday", defaultHour: 3, defaultMinute: 0, purpose: "Self-evolution" },
|
|
103
|
+
{ name: "followup-hygiene", script: "nexo-followup-hygiene.py", interpreter: "python", scriptDir: "scripts",
|
|
104
|
+
type: "weekly", defaultDay: "sunday", defaultHour: 5, defaultMinute: 0, purpose: "Cleanup stale followups" },
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Complete definition of all 7 core hooks.
|
|
109
|
+
* event: Claude Code hook event name
|
|
110
|
+
* matcher: glob matcher for the hook
|
|
111
|
+
* script: script filename inside NEXO_HOME/hooks/ (or a raw command template)
|
|
112
|
+
* key: unique identifier to detect if already registered (avoids duplicates)
|
|
113
|
+
*/
|
|
114
|
+
const ALL_CORE_HOOKS = [
|
|
115
|
+
{ event: "SessionStart", key: "session-start-ts", commandTemplate: (nexoHome) =>
|
|
116
|
+
`date +%s > ${path.join(nexoHome, "operations", ".session-start-ts")}`,
|
|
117
|
+
purpose: "Session timing" },
|
|
118
|
+
{ event: "SessionStart", key: "session-start.sh", script: "session-start.sh",
|
|
119
|
+
purpose: "Briefing + context" },
|
|
120
|
+
{ event: "Stop", key: "session-stop.sh", script: "session-stop.sh",
|
|
121
|
+
purpose: "POSTMORTEM — the most important" },
|
|
122
|
+
{ event: "PostToolUse", key: "capture-tool-logs.sh", script: "capture-tool-logs.sh",
|
|
123
|
+
purpose: "Operation capture" },
|
|
124
|
+
{ event: "PostToolUse", key: "inbox-hook.sh", script: "inbox-hook.sh",
|
|
125
|
+
purpose: "Inter-session messaging" },
|
|
126
|
+
{ event: "PreCompact", key: "pre-compact.sh", script: "pre-compact.sh",
|
|
127
|
+
purpose: "Memory preservation" },
|
|
128
|
+
{ event: "PostCompact", key: "post-compact.sh", script: "post-compact.sh",
|
|
129
|
+
purpose: "Memory restoration" },
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Register all 7 core hooks in settings.hooks.
|
|
134
|
+
* Additive: adds missing hooks, never removes user's custom ones.
|
|
135
|
+
*/
|
|
136
|
+
function registerAllCoreHooks(settings, hooksDir, nexoHome) {
|
|
137
|
+
if (!settings.hooks) settings.hooks = {};
|
|
138
|
+
|
|
139
|
+
// Ensure operations dir exists for timestamp file
|
|
140
|
+
const opsDir = path.join(nexoHome, "operations");
|
|
141
|
+
fs.mkdirSync(opsDir, { recursive: true });
|
|
142
|
+
|
|
143
|
+
for (const hook of ALL_CORE_HOOKS) {
|
|
144
|
+
if (!settings.hooks[hook.event]) settings.hooks[hook.event] = [];
|
|
145
|
+
|
|
146
|
+
// Check if this specific hook is already registered (by key)
|
|
147
|
+
const alreadyExists = settings.hooks[hook.event].some(
|
|
148
|
+
(h) => h.command && h.command.includes(hook.key)
|
|
149
|
+
);
|
|
150
|
+
if (alreadyExists) continue;
|
|
151
|
+
|
|
152
|
+
// Build the command
|
|
153
|
+
let command;
|
|
154
|
+
if (hook.commandTemplate) {
|
|
155
|
+
command = hook.commandTemplate(nexoHome);
|
|
156
|
+
} else {
|
|
157
|
+
command = `NEXO_HOME=${nexoHome} bash ${path.join(hooksDir, hook.script)}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
settings.hooks[hook.event].push({
|
|
161
|
+
type: "command",
|
|
162
|
+
command: command,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Load schedule.json if it exists, or create it with defaults on fresh install.
|
|
169
|
+
* NEVER overwrites an existing schedule.json (user customization).
|
|
170
|
+
*/
|
|
171
|
+
function loadOrCreateSchedule(nexoHome) {
|
|
172
|
+
const configDir = path.join(nexoHome, "config");
|
|
173
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
174
|
+
const scheduleFile = path.join(configDir, "schedule.json");
|
|
175
|
+
|
|
176
|
+
if (fs.existsSync(scheduleFile)) {
|
|
177
|
+
try {
|
|
178
|
+
return JSON.parse(fs.readFileSync(scheduleFile, "utf8"));
|
|
179
|
+
} catch {
|
|
180
|
+
// Corrupt file — return defaults but don't overwrite
|
|
181
|
+
return getDefaultSchedule();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Fresh install: detect timezone and create schedule.json
|
|
186
|
+
const detectedTz = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
187
|
+
const schedule = getDefaultSchedule(detectedTz);
|
|
188
|
+
fs.writeFileSync(scheduleFile, JSON.stringify(schedule, null, 2));
|
|
189
|
+
return schedule;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getDefaultSchedule(timezone) {
|
|
193
|
+
return {
|
|
194
|
+
timezone: timezone || "UTC",
|
|
195
|
+
auto_update: true,
|
|
196
|
+
processes: {
|
|
197
|
+
"cognitive-decay": { hour: 3, minute: 0 },
|
|
198
|
+
"postmortem": { hour: 23, minute: 30 },
|
|
199
|
+
"self-audit": { hour: 7, minute: 0 },
|
|
200
|
+
"sleep": { hour: 4, minute: 0 },
|
|
201
|
+
"deep-sleep": { hour: 4, minute: 30 },
|
|
202
|
+
"evolution": { day: "sunday", hour: 3, minute: 0 },
|
|
203
|
+
"followup-hygiene": { day: "sunday", hour: 5, minute: 0 },
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve the venv python path for an existing NEXO_HOME installation.
|
|
210
|
+
*/
|
|
211
|
+
function findVenvPython(nexoHome) {
|
|
212
|
+
const venvPy = path.join(nexoHome, ".venv", "bin", "python3");
|
|
213
|
+
if (fs.existsSync(venvPy)) return venvPy;
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Map day name to systemd OnCalendar day abbreviation and crontab day number.
|
|
219
|
+
*/
|
|
220
|
+
const DAY_MAP = {
|
|
221
|
+
sunday: { systemd: "Sun", cron: 0 },
|
|
222
|
+
monday: { systemd: "Mon", cron: 1 },
|
|
223
|
+
tuesday: { systemd: "Tue", cron: 2 },
|
|
224
|
+
wednesday: { systemd: "Wed", cron: 3 },
|
|
225
|
+
thursday: { systemd: "Thu", cron: 4 },
|
|
226
|
+
friday: { systemd: "Fri", cron: 5 },
|
|
227
|
+
saturday: { systemd: "Sat", cron: 6 },
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Install all 13 processes on the current platform.
|
|
232
|
+
* macOS: LaunchAgents (.plist)
|
|
233
|
+
* Linux+systemd: .service + .timer files
|
|
234
|
+
* Linux fallback: crontab entries
|
|
235
|
+
*/
|
|
236
|
+
function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir) {
|
|
237
|
+
const home = require("os").homedir();
|
|
238
|
+
const nexoCode = path.join(__dirname, "..");
|
|
239
|
+
const logsDir = path.join(nexoHome, "logs");
|
|
240
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
241
|
+
|
|
242
|
+
// Resolve script path: "root" means NEXO_HOME directly, "scripts" means NEXO_HOME/scripts/
|
|
243
|
+
function scriptPath(proc) {
|
|
244
|
+
const dir = proc.scriptDir === "root" ? nexoHome : path.join(nexoHome, "scripts");
|
|
245
|
+
return path.join(dir, proc.script);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Resolve interpreter
|
|
249
|
+
function interpreterPath(proc) {
|
|
250
|
+
return proc.interpreter === "bash" ? "/bin/bash" : pythonPath;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Get schedule overrides for daily/weekly processes
|
|
254
|
+
function getSchedule(proc) {
|
|
255
|
+
const sched = schedule.processes || {};
|
|
256
|
+
const override = sched[proc.name] || {};
|
|
257
|
+
return {
|
|
258
|
+
hour: override.hour !== undefined ? override.hour : (proc.defaultHour || 0),
|
|
259
|
+
minute: override.minute !== undefined ? override.minute : (proc.defaultMinute || 0),
|
|
260
|
+
day: override.day || proc.defaultDay || null,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (platform === "darwin") {
|
|
265
|
+
// ──── macOS: LaunchAgents ────
|
|
266
|
+
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
267
|
+
let count = 0;
|
|
268
|
+
|
|
269
|
+
for (const proc of ALL_PROCESSES) {
|
|
270
|
+
// Skip macOnly processes on Linux
|
|
271
|
+
if (proc.macOnly && platform !== "darwin") continue;
|
|
272
|
+
|
|
273
|
+
const plistName = `com.nexo.${proc.name}.plist`;
|
|
274
|
+
const plistPath = path.join(launchAgentsDir, plistName);
|
|
275
|
+
const sPath = scriptPath(proc);
|
|
276
|
+
const interp = interpreterPath(proc);
|
|
277
|
+
const s = getSchedule(proc);
|
|
278
|
+
|
|
279
|
+
let scheduleBlock = "";
|
|
280
|
+
if (proc.type === "keepAlive") {
|
|
281
|
+
scheduleBlock = ` <key>RunAtLoad</key>
|
|
282
|
+
<true/>
|
|
283
|
+
<key>KeepAlive</key>
|
|
284
|
+
<true/>`;
|
|
285
|
+
} else if (proc.type === "runAtLoad") {
|
|
286
|
+
let extra = "";
|
|
287
|
+
if (proc.watchPaths) {
|
|
288
|
+
const paths = proc.watchPaths.map(p => p.replace("~", home));
|
|
289
|
+
extra = `\n <key>WatchPaths</key>\n <array>\n${paths.map(p => ` <string>${p}</string>`).join("\n")}\n </array>`;
|
|
290
|
+
}
|
|
291
|
+
scheduleBlock = ` <key>RunAtLoad</key>
|
|
292
|
+
<true/>${extra}`;
|
|
293
|
+
} else if (proc.type === "interval") {
|
|
294
|
+
scheduleBlock = ` <key>StartInterval</key>
|
|
295
|
+
<integer>${proc.intervalMinutes * 60}</integer>
|
|
296
|
+
<key>RunAtLoad</key>
|
|
297
|
+
<false/>`;
|
|
298
|
+
} else if (proc.type === "daily") {
|
|
299
|
+
scheduleBlock = ` <key>StartCalendarInterval</key>
|
|
300
|
+
<dict>
|
|
301
|
+
<key>Hour</key>
|
|
302
|
+
<integer>${s.hour}</integer>
|
|
303
|
+
<key>Minute</key>
|
|
304
|
+
<integer>${s.minute}</integer>
|
|
305
|
+
</dict>
|
|
306
|
+
<key>RunAtLoad</key>
|
|
307
|
+
<false/>`;
|
|
308
|
+
} else if (proc.type === "weekly") {
|
|
309
|
+
// macOS uses Weekday 0=Sunday
|
|
310
|
+
const dayNum = s.day ? (DAY_MAP[s.day.toLowerCase()] || { cron: 0 }).cron : 0;
|
|
311
|
+
scheduleBlock = ` <key>StartCalendarInterval</key>
|
|
312
|
+
<dict>
|
|
313
|
+
<key>Weekday</key>
|
|
314
|
+
<integer>${dayNum}</integer>
|
|
315
|
+
<key>Hour</key>
|
|
316
|
+
<integer>${s.hour}</integer>
|
|
317
|
+
<key>Minute</key>
|
|
318
|
+
<integer>${s.minute}</integer>
|
|
319
|
+
</dict>
|
|
320
|
+
<key>RunAtLoad</key>
|
|
321
|
+
<false/>`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
325
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
326
|
+
<plist version="1.0">
|
|
327
|
+
<dict>
|
|
328
|
+
<key>Label</key>
|
|
329
|
+
<string>com.nexo.${proc.name}</string>
|
|
330
|
+
<key>ProgramArguments</key>
|
|
331
|
+
<array>
|
|
332
|
+
<string>${interp}</string>
|
|
333
|
+
<string>${sPath}</string>
|
|
334
|
+
</array>
|
|
335
|
+
${scheduleBlock}
|
|
336
|
+
<key>StandardOutPath</key>
|
|
337
|
+
<string>${path.join(logsDir, `${proc.name}-stdout.log`)}</string>
|
|
338
|
+
<key>StandardErrorPath</key>
|
|
339
|
+
<string>${path.join(logsDir, `${proc.name}-stderr.log`)}</string>
|
|
340
|
+
<key>EnvironmentVariables</key>
|
|
341
|
+
<dict>
|
|
342
|
+
<key>HOME</key>
|
|
343
|
+
<string>${home}</string>
|
|
344
|
+
<key>NEXO_HOME</key>
|
|
345
|
+
<string>${nexoHome}</string>
|
|
346
|
+
<key>NEXO_CODE</key>
|
|
347
|
+
<string>${nexoCode}</string>
|
|
348
|
+
<key>PATH</key>
|
|
349
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
350
|
+
</dict>
|
|
351
|
+
</dict>
|
|
352
|
+
</plist>`;
|
|
353
|
+
|
|
354
|
+
fs.writeFileSync(plistPath, plist);
|
|
355
|
+
try {
|
|
356
|
+
execSync(
|
|
357
|
+
`launchctl bootout gui/$(id -u) "${plistPath}" 2>/dev/null; launchctl bootstrap gui/$(id -u) "${plistPath}"`,
|
|
358
|
+
{ stdio: "pipe" }
|
|
359
|
+
);
|
|
360
|
+
} catch {
|
|
361
|
+
// May fail if not previously loaded, that's OK
|
|
362
|
+
}
|
|
363
|
+
count++;
|
|
364
|
+
}
|
|
365
|
+
log(`${count} automated processes configured (LaunchAgents).`);
|
|
366
|
+
|
|
367
|
+
} else if (platform === "linux") {
|
|
368
|
+
// ──── Linux: systemd user timers (preferred) or crontab fallback ────
|
|
369
|
+
const systemdDir = path.join(home, ".config", "systemd", "user");
|
|
370
|
+
const hasSystemd = run("which systemctl") && run("systemctl --user status 2>/dev/null");
|
|
371
|
+
|
|
372
|
+
if (hasSystemd) {
|
|
373
|
+
fs.mkdirSync(systemdDir, { recursive: true });
|
|
374
|
+
let count = 0;
|
|
375
|
+
|
|
376
|
+
for (const proc of ALL_PROCESSES) {
|
|
377
|
+
if (proc.macOnly) continue; // tcc-approve is macOS only
|
|
378
|
+
const serviceName = `nexo-${proc.name}`;
|
|
379
|
+
const serviceFile = path.join(systemdDir, `${serviceName}.service`);
|
|
380
|
+
const timerFile = path.join(systemdDir, `${serviceName}.timer`);
|
|
381
|
+
const sPath = scriptPath(proc);
|
|
382
|
+
const interp = interpreterPath(proc);
|
|
383
|
+
const s = getSchedule(proc);
|
|
384
|
+
|
|
385
|
+
const serviceType = proc.type === "keepAlive" ? "simple" : "oneshot";
|
|
386
|
+
const restartPolicy = proc.type === "keepAlive" ? "Restart=always\nRestartSec=5" : "";
|
|
387
|
+
const service = `[Unit]
|
|
388
|
+
Description=NEXO Brain — ${proc.name} (${proc.purpose})
|
|
389
|
+
|
|
390
|
+
[Service]
|
|
391
|
+
Type=${serviceType}
|
|
392
|
+
ExecStart=${interp} ${sPath}
|
|
393
|
+
Environment=HOME=${home}
|
|
394
|
+
Environment=NEXO_HOME=${nexoHome}
|
|
395
|
+
Environment=NEXO_CODE=${nexoCode}
|
|
396
|
+
StandardOutput=append:${path.join(logsDir, `${proc.name}-stdout.log`)}
|
|
397
|
+
StandardError=append:${path.join(logsDir, `${proc.name}-stderr.log`)}
|
|
398
|
+
${restartPolicy}
|
|
399
|
+
`;
|
|
400
|
+
|
|
401
|
+
// Build calendar spec
|
|
402
|
+
let onCalendar = "";
|
|
403
|
+
let persistent = "true";
|
|
404
|
+
if (proc.type === "keepAlive") {
|
|
405
|
+
// KeepAlive = persistent service, no timer needed
|
|
406
|
+
fs.writeFileSync(serviceFile, service + `\n[Install]\nWantedBy=default.target\n`);
|
|
407
|
+
run(`systemctl --user enable ${serviceName}.service`);
|
|
408
|
+
run(`systemctl --user start ${serviceName}.service`);
|
|
409
|
+
count++;
|
|
410
|
+
continue;
|
|
411
|
+
} else if (proc.type === "runAtLoad") {
|
|
412
|
+
// No timer for runAtLoad — runs via MCP startup
|
|
413
|
+
fs.writeFileSync(serviceFile, service);
|
|
414
|
+
count++;
|
|
415
|
+
continue;
|
|
416
|
+
} else if (proc.type === "interval") {
|
|
417
|
+
onCalendar = `*:0/${proc.intervalMinutes}`;
|
|
418
|
+
} else if (proc.type === "daily") {
|
|
419
|
+
onCalendar = `*-*-* ${String(s.hour).padStart(2, "0")}:${String(s.minute).padStart(2, "0")}:00`;
|
|
420
|
+
} else if (proc.type === "weekly") {
|
|
421
|
+
const dayAbbr = s.day ? (DAY_MAP[s.day.toLowerCase()] || { systemd: "Sun" }).systemd : "Sun";
|
|
422
|
+
onCalendar = `${dayAbbr} *-*-* ${String(s.hour).padStart(2, "0")}:${String(s.minute).padStart(2, "0")}:00`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const timer = `[Unit]
|
|
426
|
+
Description=NEXO Brain — ${proc.name} timer
|
|
427
|
+
|
|
428
|
+
[Timer]
|
|
429
|
+
OnCalendar=${onCalendar}
|
|
430
|
+
Persistent=${persistent}
|
|
431
|
+
|
|
432
|
+
[Install]
|
|
433
|
+
WantedBy=timers.target
|
|
434
|
+
`;
|
|
435
|
+
|
|
436
|
+
fs.writeFileSync(serviceFile, service);
|
|
437
|
+
fs.writeFileSync(timerFile, timer);
|
|
438
|
+
try {
|
|
439
|
+
execSync(`systemctl --user enable --now ${serviceName}.timer 2>/dev/null`, { stdio: "pipe" });
|
|
440
|
+
} catch {}
|
|
441
|
+
count++;
|
|
442
|
+
}
|
|
443
|
+
log(`${count} systemd user timers configured.`);
|
|
444
|
+
|
|
445
|
+
} else {
|
|
446
|
+
// ──── Fallback: crontab ────
|
|
447
|
+
log("systemd not available, configuring crontab...");
|
|
448
|
+
const cronLines = [];
|
|
449
|
+
const envLine = `NEXO_HOME=${nexoHome}`;
|
|
450
|
+
const envLine2 = `NEXO_CODE=${nexoCode}`;
|
|
451
|
+
|
|
452
|
+
for (const proc of ALL_PROCESSES) {
|
|
453
|
+
if (proc.type === "runAtLoad") continue; // No cron for runAtLoad
|
|
454
|
+
const sPath = scriptPath(proc);
|
|
455
|
+
const interp = interpreterPath(proc);
|
|
456
|
+
const s = getSchedule(proc);
|
|
457
|
+
const logPath = path.join(logsDir, `${proc.name}-stdout.log`);
|
|
458
|
+
|
|
459
|
+
let cronSpec = "";
|
|
460
|
+
if (proc.type === "interval") {
|
|
461
|
+
cronSpec = `*/${proc.intervalMinutes} * * * *`;
|
|
462
|
+
} else if (proc.type === "daily") {
|
|
463
|
+
cronSpec = `${s.minute} ${s.hour} * * *`;
|
|
464
|
+
} else if (proc.type === "weekly") {
|
|
465
|
+
const dayNum = s.day ? (DAY_MAP[s.day.toLowerCase()] || { cron: 0 }).cron : 0;
|
|
466
|
+
cronSpec = `${s.minute} ${s.hour} * * ${dayNum}`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
cronLines.push(`${cronSpec} ${interp} ${sPath} >> ${logPath} 2>&1`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
const existingCron = run("crontab -l 2>/dev/null") || "";
|
|
474
|
+
const nexoCronMarker = "# NEXO Brain automated processes";
|
|
475
|
+
const nexoCronEnd = "# END NEXO Brain";
|
|
476
|
+
|
|
477
|
+
// Remove old NEXO cron block if present, then add fresh one
|
|
478
|
+
let baseCron = existingCron;
|
|
479
|
+
if (existingCron.includes(nexoCronMarker)) {
|
|
480
|
+
const startIdx = existingCron.indexOf(nexoCronMarker);
|
|
481
|
+
const endIdx = existingCron.indexOf(nexoCronEnd);
|
|
482
|
+
if (endIdx > startIdx) {
|
|
483
|
+
baseCron = existingCron.substring(0, startIdx) + existingCron.substring(endIdx + nexoCronEnd.length);
|
|
484
|
+
} else {
|
|
485
|
+
baseCron = existingCron.substring(0, startIdx);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const newCron = baseCron.trimEnd() + "\n" + nexoCronMarker + "\n" + envLine + "\n" + envLine2 + "\n" + cronLines.join("\n") + "\n" + nexoCronEnd + "\n";
|
|
490
|
+
const tmpCron = path.join(nexoHome, ".crontab-tmp");
|
|
491
|
+
fs.writeFileSync(tmpCron, newCron);
|
|
492
|
+
execSync(`crontab ${tmpCron}`, { stdio: "pipe" });
|
|
493
|
+
fs.unlinkSync(tmpCron);
|
|
494
|
+
log(`${cronLines.length} cron jobs configured.`);
|
|
495
|
+
} catch (e) {
|
|
496
|
+
log(`Could not configure crontab: ${e.message}`);
|
|
497
|
+
log("Background tasks will run via catch-up on startup.");
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
log("Unsupported platform for background tasks. Maintenance runs on MCP startup.");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
55
505
|
async function main() {
|
|
56
506
|
// Non-interactive mode: --defaults or --yes skips all prompts
|
|
57
507
|
const useDefaults = process.argv.includes("--defaults") || process.argv.includes("--yes") || process.argv.includes("-y");
|
|
@@ -107,35 +557,60 @@ async function main() {
|
|
|
107
557
|
log(`Existing installation detected: v${installedVersion} → v${currentVersion}`);
|
|
108
558
|
log("Running auto-migration...");
|
|
109
559
|
|
|
110
|
-
//
|
|
111
|
-
const
|
|
560
|
+
// Recursive copy helper (skips __pycache__, .pyc, .db files)
|
|
561
|
+
const srcDir = path.join(__dirname, "..", "src");
|
|
562
|
+
const copyDirRec = (src, dest) => {
|
|
563
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
564
|
+
fs.readdirSync(src).forEach(item => {
|
|
565
|
+
if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db")) return;
|
|
566
|
+
const srcPath = path.join(src, item);
|
|
567
|
+
const destPath = path.join(dest, item);
|
|
568
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
569
|
+
copyDirRec(srcPath, destPath);
|
|
570
|
+
} else {
|
|
571
|
+
fs.copyFileSync(srcPath, destPath);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// Update hooks (entire directory)
|
|
577
|
+
const hooksSrc = path.join(srcDir, "hooks");
|
|
112
578
|
const hooksDest = path.join(NEXO_HOME, "hooks");
|
|
113
|
-
fs.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
});
|
|
579
|
+
if (fs.existsSync(hooksSrc)) {
|
|
580
|
+
copyDirRec(hooksSrc, hooksDest);
|
|
581
|
+
// Make .sh files executable
|
|
582
|
+
fs.readdirSync(hooksDest).filter(f => f.endsWith(".sh")).forEach(f => {
|
|
583
|
+
fs.chmodSync(path.join(hooksDest, f), "755");
|
|
584
|
+
});
|
|
585
|
+
}
|
|
122
586
|
log(" Hooks updated.");
|
|
123
587
|
|
|
124
|
-
// Update core Python files
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
588
|
+
// Update core Python files (flat .py files in src/)
|
|
589
|
+
const coreFlatFiles = [
|
|
590
|
+
"server.py", "plugin_loader.py",
|
|
591
|
+
"knowledge_graph.py", "kg_populate.py", "maintenance.py", "storage_router.py",
|
|
592
|
+
"claim_graph.py", "hnsw_index.py", "evolution_cycle.py", "migrate_embeddings.py",
|
|
593
|
+
"auto_close_sessions.py",
|
|
594
|
+
"tools_sessions.py", "tools_coordination.py", "tools_reminders.py",
|
|
595
|
+
"tools_reminders_crud.py", "tools_learnings.py", "tools_credentials.py",
|
|
596
|
+
"tools_task_history.py", "tools_menu.py",
|
|
597
|
+
];
|
|
598
|
+
coreFlatFiles.forEach((f) => {
|
|
131
599
|
const src = path.join(srcDir, f);
|
|
132
600
|
if (fs.existsSync(src)) {
|
|
133
601
|
fs.copyFileSync(src, path.join(NEXO_HOME, f));
|
|
134
602
|
}
|
|
135
603
|
});
|
|
604
|
+
// Update core packages (db/, cognitive/) — full directory copy
|
|
605
|
+
["db", "cognitive"].forEach(pkg => {
|
|
606
|
+
const pkgSrc = path.join(srcDir, pkg);
|
|
607
|
+
if (fs.existsSync(pkgSrc)) {
|
|
608
|
+
copyDirRec(pkgSrc, path.join(NEXO_HOME, pkg));
|
|
609
|
+
}
|
|
610
|
+
});
|
|
136
611
|
log(" Core files updated.");
|
|
137
612
|
|
|
138
|
-
// Update plugins
|
|
613
|
+
// Update plugins (all .py files in plugins/)
|
|
139
614
|
const pluginsSrc = path.join(srcDir, "plugins");
|
|
140
615
|
const pluginsDest = path.join(NEXO_HOME, "plugins");
|
|
141
616
|
fs.mkdirSync(pluginsDest, { recursive: true });
|
|
@@ -146,57 +621,51 @@ async function main() {
|
|
|
146
621
|
}
|
|
147
622
|
log(" Plugins updated.");
|
|
148
623
|
|
|
149
|
-
// Update dashboard
|
|
624
|
+
// Update dashboard (recursive — includes static/, templates/)
|
|
150
625
|
const dashSrc = path.join(srcDir, "dashboard");
|
|
151
626
|
const dashDest = path.join(NEXO_HOME, "dashboard");
|
|
152
627
|
if (fs.existsSync(dashSrc)) {
|
|
153
|
-
|
|
154
|
-
const copyDir = (src, dest) => {
|
|
155
|
-
fs.readdirSync(src).forEach(item => {
|
|
156
|
-
const srcPath = path.join(src, item);
|
|
157
|
-
const destPath = path.join(dest, item);
|
|
158
|
-
if (fs.statSync(srcPath).isDirectory()) {
|
|
159
|
-
fs.mkdirSync(destPath, { recursive: true });
|
|
160
|
-
copyDir(srcPath, destPath);
|
|
161
|
-
} else {
|
|
162
|
-
fs.copyFileSync(srcPath, destPath);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
};
|
|
166
|
-
copyDir(dashSrc, dashDest);
|
|
628
|
+
copyDirRec(dashSrc, dashDest);
|
|
167
629
|
log(" Dashboard updated.");
|
|
168
630
|
}
|
|
169
631
|
|
|
170
|
-
// Update
|
|
632
|
+
// Update rules (directory with core-rules.json, __init__.py, migrate.py)
|
|
633
|
+
const rulesSrc = path.join(srcDir, "rules");
|
|
634
|
+
const rulesDest = path.join(NEXO_HOME, "rules");
|
|
635
|
+
if (fs.existsSync(rulesSrc)) {
|
|
636
|
+
copyDirRec(rulesSrc, rulesDest);
|
|
637
|
+
log(" Rules updated.");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Update scripts (all .py, .sh files + subdirectories like deep-sleep/)
|
|
171
641
|
const scriptsSrc = path.join(srcDir, "scripts");
|
|
172
642
|
const scriptsDest = path.join(NEXO_HOME, "scripts");
|
|
173
|
-
fs.mkdirSync(scriptsDest, { recursive: true });
|
|
174
643
|
if (fs.existsSync(scriptsSrc)) {
|
|
175
|
-
|
|
176
|
-
|
|
644
|
+
copyDirRec(scriptsSrc, scriptsDest);
|
|
645
|
+
// Make .sh files executable
|
|
646
|
+
fs.readdirSync(scriptsDest).filter(f => f.endsWith(".sh")).forEach(f => {
|
|
647
|
+
fs.chmodSync(path.join(scriptsDest, f), "755");
|
|
177
648
|
});
|
|
178
649
|
}
|
|
179
650
|
log(" Scripts updated.");
|
|
180
651
|
|
|
181
|
-
//
|
|
652
|
+
// Register ALL 7 core hooks in settings.json (additive — don't remove user's custom hooks)
|
|
182
653
|
let settings = {};
|
|
183
654
|
if (fs.existsSync(CLAUDE_SETTINGS)) {
|
|
184
655
|
try { settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS, "utf8")); } catch {}
|
|
185
656
|
}
|
|
186
|
-
if (settings.hooks
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
}
|
|
657
|
+
if (!settings.hooks) settings.hooks = {};
|
|
658
|
+
const migHooksDest = path.join(NEXO_HOME, "hooks");
|
|
659
|
+
registerAllCoreHooks(settings, migHooksDest, NEXO_HOME);
|
|
660
|
+
fs.mkdirSync(path.dirname(CLAUDE_SETTINGS), { recursive: true });
|
|
661
|
+
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
662
|
+
log(" All 7 core hooks registered in Claude Code settings.");
|
|
663
|
+
|
|
664
|
+
// Regenerate ALL 13 LaunchAgents / systemd timers
|
|
665
|
+
const migSchedule = loadOrCreateSchedule(NEXO_HOME);
|
|
666
|
+
const migPython = findVenvPython(NEXO_HOME) || "python3";
|
|
667
|
+
installAllProcesses(platform, migPython, NEXO_HOME, migSchedule, LAUNCH_AGENTS);
|
|
668
|
+
log(" All 13 automated processes updated.");
|
|
200
669
|
|
|
201
670
|
// Update version file
|
|
202
671
|
fs.writeFileSync(versionFile, JSON.stringify({
|
|
@@ -215,7 +684,20 @@ async function main() {
|
|
|
215
684
|
.replace(/\{\{NEXO_HOME\}\}/g, NEXO_HOME);
|
|
216
685
|
fs.writeFileSync(path.join(NEXO_HOME, "CLAUDE.md.updated"), claudeMd);
|
|
217
686
|
log(` Updated CLAUDE.md template saved to ~/.nexo/CLAUDE.md.updated`);
|
|
218
|
-
|
|
687
|
+
|
|
688
|
+
// Update CLAUDE.md version tracker (auto_update.py will handle section migration on next server start)
|
|
689
|
+
const migClaudeMdVerMatch = claudeMd.match(/nexo-claude-md-version:\s*([\d.]+)/);
|
|
690
|
+
if (migClaudeMdVerMatch) {
|
|
691
|
+
const migDataDir = path.join(NEXO_HOME, "data");
|
|
692
|
+
fs.mkdirSync(migDataDir, { recursive: true });
|
|
693
|
+
// Don't write the version yet — let auto_update.py detect the diff and migrate sections
|
|
694
|
+
// Only write if no version file exists (first time with version tracking)
|
|
695
|
+
const migVerFile = path.join(migDataDir, "claude_md_version.txt");
|
|
696
|
+
if (!fs.existsSync(migVerFile)) {
|
|
697
|
+
fs.writeFileSync(migVerFile, "0.0.0");
|
|
698
|
+
log(` CLAUDE.md version tracker initialized (will migrate on next server start)`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
219
701
|
}
|
|
220
702
|
|
|
221
703
|
console.log("");
|
|
@@ -298,6 +780,8 @@ async function main() {
|
|
|
298
780
|
const i18n = {
|
|
299
781
|
en: {
|
|
300
782
|
langConfirm: "English it is.",
|
|
783
|
+
askDataDir: ` Where should I store my data? (databases, backups, personal plugins)\n Default: ~/.nexo/\n > `,
|
|
784
|
+
dataDirConfirm: (p) => `Data directory: ${p}`,
|
|
301
785
|
askUserName: " What's your name? > ",
|
|
302
786
|
userGreet: (n) => `Nice to meet you, ${n}.`,
|
|
303
787
|
askAgentName: " What should I call myself? (default: NEXO) > ",
|
|
@@ -325,6 +809,8 @@ async function main() {
|
|
|
325
809
|
},
|
|
326
810
|
es: {
|
|
327
811
|
langConfirm: "Español, perfecto.",
|
|
812
|
+
askDataDir: ` ¿Dónde quieres que guarde mis datos? (bases de datos, backups, plugins personales)\n Por defecto: ~/.nexo/\n > `,
|
|
813
|
+
dataDirConfirm: (p) => `Directorio de datos: ${p}`,
|
|
328
814
|
askUserName: " ¿Cómo te llamas? > ",
|
|
329
815
|
userGreet: (n) => `Encantado, ${n}.`,
|
|
330
816
|
askAgentName: " ¿Cómo quieres que me llame? (default: NEXO) > ",
|
|
@@ -352,6 +838,8 @@ async function main() {
|
|
|
352
838
|
},
|
|
353
839
|
fr: {
|
|
354
840
|
langConfirm: "Français, parfait.",
|
|
841
|
+
askDataDir: ` Où stocker mes données ? (bases de données, sauvegardes, plugins)\n Par défaut : ~/.nexo/\n > `,
|
|
842
|
+
dataDirConfirm: (p) => `Répertoire de données : ${p}`,
|
|
355
843
|
askUserName: " Comment tu t'appelles ? > ",
|
|
356
844
|
userGreet: (n) => `Enchanté, ${n}.`,
|
|
357
845
|
askAgentName: " Comment veux-tu m'appeler ? (défaut: NEXO) > ",
|
|
@@ -379,6 +867,8 @@ async function main() {
|
|
|
379
867
|
},
|
|
380
868
|
de: {
|
|
381
869
|
langConfirm: "Deutsch, perfekt.",
|
|
870
|
+
askDataDir: ` Wo sollen meine Daten gespeichert werden? (Datenbanken, Backups, Plugins)\n Standard: ~/.nexo/\n > `,
|
|
871
|
+
dataDirConfirm: (p) => `Datenverzeichnis: ${p}`,
|
|
382
872
|
askUserName: " Wie heißt du? > ",
|
|
383
873
|
userGreet: (n) => `Freut mich, ${n}.`,
|
|
384
874
|
askAgentName: " Wie soll ich heißen? (Standard: NEXO) > ",
|
|
@@ -406,6 +896,8 @@ async function main() {
|
|
|
406
896
|
},
|
|
407
897
|
it: {
|
|
408
898
|
langConfirm: "Italiano, perfetto.",
|
|
899
|
+
askDataDir: ` Dove salvare i miei dati? (database, backup, plugin)\n Default: ~/.nexo/\n > `,
|
|
900
|
+
dataDirConfirm: (p) => `Directory dati: ${p}`,
|
|
409
901
|
askUserName: " Come ti chiami? > ",
|
|
410
902
|
userGreet: (n) => `Piacere, ${n}.`,
|
|
411
903
|
askAgentName: " Come vuoi chiamarmi? (default: NEXO) > ",
|
|
@@ -433,6 +925,8 @@ async function main() {
|
|
|
433
925
|
},
|
|
434
926
|
pt: {
|
|
435
927
|
langConfirm: "Português, perfeito.",
|
|
928
|
+
askDataDir: ` Onde guardar os meus dados? (bases de dados, backups, plugins)\n Padrão: ~/.nexo/\n > `,
|
|
929
|
+
dataDirConfirm: (p) => `Diretório de dados: ${p}`,
|
|
436
930
|
askUserName: " Como te chamas? > ",
|
|
437
931
|
userGreet: (n) => `Prazer, ${n}.`,
|
|
438
932
|
askAgentName: " Como queres que eu me chame? (padrão: NEXO) > ",
|
|
@@ -486,6 +980,20 @@ async function main() {
|
|
|
486
980
|
console.log("");
|
|
487
981
|
}
|
|
488
982
|
|
|
983
|
+
// Step 1b: Data directory
|
|
984
|
+
if (!useDefaults) {
|
|
985
|
+
const dataDirInput = await ask(t.askDataDir);
|
|
986
|
+
const dataDirTrimmed = dataDirInput.trim();
|
|
987
|
+
if (dataDirTrimmed) {
|
|
988
|
+
// Expand ~ to home dir
|
|
989
|
+
NEXO_HOME = dataDirTrimmed.replace(/^~/, require("os").homedir());
|
|
990
|
+
// Resolve to absolute path
|
|
991
|
+
NEXO_HOME = path.resolve(NEXO_HOME);
|
|
992
|
+
}
|
|
993
|
+
log(t.dataDirConfirm(NEXO_HOME));
|
|
994
|
+
console.log("");
|
|
995
|
+
}
|
|
996
|
+
|
|
489
997
|
// Step 2: User's name (P2)
|
|
490
998
|
let userName = "";
|
|
491
999
|
if (!useDefaults) {
|
|
@@ -598,7 +1106,8 @@ async function main() {
|
|
|
598
1106
|
|
|
599
1107
|
// Use venv python if available, otherwise fall back to system python with --break-system-packages
|
|
600
1108
|
const pipPython = fs.existsSync(venvPython) ? venvPython : python;
|
|
601
|
-
const
|
|
1109
|
+
const requirementsFile = path.join(__dirname, "..", "src", "requirements.txt");
|
|
1110
|
+
const pipArgs = ["-m", "pip", "install", "--quiet", "-r", requirementsFile];
|
|
602
1111
|
if (!fs.existsSync(venvPython)) {
|
|
603
1112
|
pipArgs.push("--break-system-packages"); // Fallback for systems without venv
|
|
604
1113
|
}
|
|
@@ -606,7 +1115,7 @@ async function main() {
|
|
|
606
1115
|
const pipInstall = spawnSync(pipPython, pipArgs, { stdio: "inherit" });
|
|
607
1116
|
if (pipInstall.status !== 0) {
|
|
608
1117
|
log("Failed to install Python dependencies.");
|
|
609
|
-
log("Try manually: python3 -m venv ~/.nexo/.venv && ~/.nexo/.venv/bin/pip install
|
|
1118
|
+
log("Try manually: python3 -m venv ~/.nexo/.venv && ~/.nexo/.venv/bin/pip install -r src/requirements.txt");
|
|
610
1119
|
process.exit(1);
|
|
611
1120
|
}
|
|
612
1121
|
// Update python reference to use venv python for the rest of setup
|
|
@@ -625,9 +1134,33 @@ async function main() {
|
|
|
625
1134
|
path.join(NEXO_HOME, "backups"),
|
|
626
1135
|
path.join(NEXO_HOME, "coordination"),
|
|
627
1136
|
path.join(NEXO_HOME, "brain"),
|
|
1137
|
+
path.join(NEXO_HOME, "config"),
|
|
1138
|
+
path.join(NEXO_HOME, "operations"),
|
|
628
1139
|
];
|
|
629
1140
|
dirs.forEach((d) => fs.mkdirSync(d, { recursive: true }));
|
|
630
1141
|
|
|
1142
|
+
// Create default evolution-objective.json in brain/ if it doesn't exist
|
|
1143
|
+
const evoObjectivePath = path.join(NEXO_HOME, "brain", "evolution-objective.json");
|
|
1144
|
+
if (!fs.existsSync(evoObjectivePath)) {
|
|
1145
|
+
fs.writeFileSync(evoObjectivePath, JSON.stringify({
|
|
1146
|
+
objective: "Improve operational excellence and reduce repeated errors",
|
|
1147
|
+
focus_areas: ["error_prevention", "proactivity", "memory_quality"],
|
|
1148
|
+
evolution_enabled: true,
|
|
1149
|
+
evolution_mode: "review",
|
|
1150
|
+
dimensions: {
|
|
1151
|
+
episodic_memory: { current: 0, target: 90 },
|
|
1152
|
+
autonomy: { current: 0, target: 80 },
|
|
1153
|
+
proactivity: { current: 0, target: 70 },
|
|
1154
|
+
self_improvement: { current: 0, target: 60 },
|
|
1155
|
+
agi: { current: 0, target: 20 },
|
|
1156
|
+
},
|
|
1157
|
+
total_evolutions: 0,
|
|
1158
|
+
consecutive_failures: 0,
|
|
1159
|
+
created_at: new Date().toISOString(),
|
|
1160
|
+
}, null, 2));
|
|
1161
|
+
log(" Created default evolution-objective.json in brain/");
|
|
1162
|
+
}
|
|
1163
|
+
|
|
631
1164
|
// Write version file for auto-update tracking
|
|
632
1165
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
633
1166
|
fs.writeFileSync(
|
|
@@ -644,16 +1177,38 @@ async function main() {
|
|
|
644
1177
|
|
|
645
1178
|
// Copy source files
|
|
646
1179
|
const srcDir = path.join(__dirname, "..", "src");
|
|
647
|
-
const
|
|
648
|
-
const
|
|
1180
|
+
const pluginsSrcDir = path.join(srcDir, "plugins");
|
|
1181
|
+
const scriptsSrcDir = path.join(srcDir, "scripts");
|
|
649
1182
|
const templateDir = path.join(__dirname, "..", "templates");
|
|
650
1183
|
|
|
651
|
-
//
|
|
1184
|
+
// Recursive copy helper (skips __pycache__, .pyc, .db files)
|
|
1185
|
+
const copyDirRecursive = (src, dest) => {
|
|
1186
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
1187
|
+
fs.readdirSync(src).forEach(item => {
|
|
1188
|
+
if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db")) return;
|
|
1189
|
+
const srcPath = path.join(src, item);
|
|
1190
|
+
const destPath = path.join(dest, item);
|
|
1191
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
1192
|
+
copyDirRecursive(srcPath, destPath);
|
|
1193
|
+
} else {
|
|
1194
|
+
fs.copyFileSync(srcPath, destPath);
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// Core flat files (single .py files in src/)
|
|
652
1200
|
const coreFiles = [
|
|
653
1201
|
"server.py",
|
|
654
|
-
"db.py",
|
|
655
1202
|
"plugin_loader.py",
|
|
656
|
-
"
|
|
1203
|
+
"knowledge_graph.py",
|
|
1204
|
+
"kg_populate.py",
|
|
1205
|
+
"maintenance.py",
|
|
1206
|
+
"storage_router.py",
|
|
1207
|
+
"claim_graph.py",
|
|
1208
|
+
"hnsw_index.py",
|
|
1209
|
+
"evolution_cycle.py",
|
|
1210
|
+
"migrate_embeddings.py",
|
|
1211
|
+
"auto_close_sessions.py",
|
|
657
1212
|
"tools_sessions.py",
|
|
658
1213
|
"tools_coordination.py",
|
|
659
1214
|
"tools_reminders.py",
|
|
@@ -662,12 +1217,6 @@ async function main() {
|
|
|
662
1217
|
"tools_credentials.py",
|
|
663
1218
|
"tools_task_history.py",
|
|
664
1219
|
"tools_menu.py",
|
|
665
|
-
"knowledge_graph.py",
|
|
666
|
-
"kg_populate.py",
|
|
667
|
-
"maintenance.py",
|
|
668
|
-
"storage_router.py",
|
|
669
|
-
"migrate_embeddings.py",
|
|
670
|
-
"auto_close_sessions.py",
|
|
671
1220
|
];
|
|
672
1221
|
coreFiles.forEach((f) => {
|
|
673
1222
|
const src = path.join(srcDir, f);
|
|
@@ -676,58 +1225,58 @@ async function main() {
|
|
|
676
1225
|
}
|
|
677
1226
|
});
|
|
678
1227
|
|
|
679
|
-
//
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
"cognitive_memory.py",
|
|
685
|
-
"entities.py",
|
|
686
|
-
"preferences.py",
|
|
687
|
-
"agents.py",
|
|
688
|
-
"backup.py",
|
|
689
|
-
"evolution.py",
|
|
690
|
-
"adaptive_mode.py",
|
|
691
|
-
"knowledge_graph_tools.py",
|
|
692
|
-
];
|
|
693
|
-
pluginFiles.forEach((f) => {
|
|
694
|
-
const src = path.join(pluginsSrcDir, f);
|
|
695
|
-
if (fs.existsSync(src)) {
|
|
696
|
-
fs.copyFileSync(src, path.join(NEXO_HOME, "plugins", f));
|
|
1228
|
+
// Core packages (directories with __init__.py)
|
|
1229
|
+
["db", "cognitive"].forEach(pkg => {
|
|
1230
|
+
const pkgSrc = path.join(srcDir, pkg);
|
|
1231
|
+
if (fs.existsSync(pkgSrc)) {
|
|
1232
|
+
copyDirRecursive(pkgSrc, path.join(NEXO_HOME, pkg));
|
|
697
1233
|
}
|
|
698
1234
|
});
|
|
699
1235
|
|
|
700
|
-
//
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
fs.copyFileSync(src, path.join(NEXO_HOME, "scripts", f));
|
|
708
|
-
}
|
|
709
|
-
});
|
|
1236
|
+
// Plugins (all .py files in plugins/)
|
|
1237
|
+
fs.mkdirSync(path.join(NEXO_HOME, "plugins"), { recursive: true });
|
|
1238
|
+
if (fs.existsSync(pluginsSrcDir)) {
|
|
1239
|
+
fs.readdirSync(pluginsSrcDir).filter(f => f.endsWith(".py")).forEach((f) => {
|
|
1240
|
+
fs.copyFileSync(path.join(pluginsSrcDir, f), path.join(NEXO_HOME, "plugins", f));
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
710
1243
|
|
|
711
|
-
//
|
|
1244
|
+
// Scripts (all files + subdirectories like deep-sleep/)
|
|
1245
|
+
if (fs.existsSync(scriptsSrcDir)) {
|
|
1246
|
+
copyDirRecursive(scriptsSrcDir, path.join(NEXO_HOME, "scripts"));
|
|
1247
|
+
// Make .sh files executable
|
|
1248
|
+
const scriptsDest = path.join(NEXO_HOME, "scripts");
|
|
1249
|
+
fs.readdirSync(scriptsDest).filter(f => f.endsWith(".sh")).forEach(f => {
|
|
1250
|
+
fs.chmodSync(path.join(scriptsDest, f), "755");
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Dashboard (recursive — includes static/, templates/)
|
|
712
1255
|
const dashSrcDir = path.join(srcDir, "dashboard");
|
|
713
|
-
const dashDestDir = path.join(NEXO_HOME, "dashboard");
|
|
714
1256
|
if (fs.existsSync(dashSrcDir)) {
|
|
715
|
-
|
|
716
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
717
|
-
fs.readdirSync(src).forEach(item => {
|
|
718
|
-
const srcPath = path.join(src, item);
|
|
719
|
-
const destPath = path.join(dest, item);
|
|
720
|
-
if (fs.statSync(srcPath).isDirectory()) {
|
|
721
|
-
copyDirRecursive(srcPath, destPath);
|
|
722
|
-
} else {
|
|
723
|
-
fs.copyFileSync(srcPath, destPath);
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
};
|
|
727
|
-
copyDirRecursive(dashSrcDir, dashDestDir);
|
|
1257
|
+
copyDirRecursive(dashSrcDir, path.join(NEXO_HOME, "dashboard"));
|
|
728
1258
|
log(" Dashboard installed.");
|
|
729
1259
|
}
|
|
730
1260
|
|
|
1261
|
+
// Rules directory
|
|
1262
|
+
const rulesSrcDir = path.join(srcDir, "rules");
|
|
1263
|
+
if (fs.existsSync(rulesSrcDir)) {
|
|
1264
|
+
copyDirRecursive(rulesSrcDir, path.join(NEXO_HOME, "rules"));
|
|
1265
|
+
log(" Rules installed.");
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Hooks directory
|
|
1269
|
+
const hooksSrcDir = path.join(srcDir, "hooks");
|
|
1270
|
+
if (fs.existsSync(hooksSrcDir)) {
|
|
1271
|
+
const hooksDest = path.join(NEXO_HOME, "hooks");
|
|
1272
|
+
copyDirRecursive(hooksSrcDir, hooksDest);
|
|
1273
|
+
// Make .sh files executable
|
|
1274
|
+
fs.readdirSync(hooksDest).filter(f => f.endsWith(".sh")).forEach(f => {
|
|
1275
|
+
fs.chmodSync(path.join(hooksDest, f), "755");
|
|
1276
|
+
});
|
|
1277
|
+
log(" Hooks installed.");
|
|
1278
|
+
}
|
|
1279
|
+
|
|
731
1280
|
// Generate personality
|
|
732
1281
|
const personality = `# ${operatorName} — Personality
|
|
733
1282
|
|
|
@@ -1192,206 +1741,26 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
1192
1741
|
},
|
|
1193
1742
|
};
|
|
1194
1743
|
|
|
1195
|
-
// Configure hooks for session capture (Sensory Register)
|
|
1744
|
+
// Configure ALL 7 core hooks for session capture (Sensory Register)
|
|
1196
1745
|
if (!settings.hooks) settings.hooks = {};
|
|
1197
1746
|
|
|
1198
|
-
//
|
|
1199
|
-
const hooksSrcDir = path.join(__dirname, "..", "src", "hooks");
|
|
1747
|
+
// Hook scripts already copied above — just reference the dest dir
|
|
1200
1748
|
const hooksDestDir = path.join(NEXO_HOME, "hooks");
|
|
1201
|
-
fs.mkdirSync(hooksDestDir, { recursive: true });
|
|
1202
|
-
["session-start.sh", "capture-session.sh", "session-stop.sh", "pre-compact.sh"].forEach((h) => {
|
|
1203
|
-
const src = path.join(hooksSrcDir, h);
|
|
1204
|
-
const dest = path.join(hooksDestDir, h);
|
|
1205
|
-
if (fs.existsSync(src)) {
|
|
1206
|
-
fs.copyFileSync(src, dest);
|
|
1207
|
-
fs.chmodSync(dest, "755");
|
|
1208
|
-
}
|
|
1209
|
-
});
|
|
1210
|
-
|
|
1211
|
-
// SessionStart hook
|
|
1212
|
-
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
|
|
1213
|
-
const startHook = {
|
|
1214
|
-
type: "command",
|
|
1215
|
-
command: `bash ${path.join(hooksDestDir, "session-start.sh")}`,
|
|
1216
|
-
};
|
|
1217
|
-
if (!settings.hooks.SessionStart.some((h) => h.command && h.command.includes("session-start.sh"))) {
|
|
1218
|
-
settings.hooks.SessionStart.push(startHook);
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
// PostToolUse hook (captures tool usage to session_buffer)
|
|
1222
|
-
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
1223
|
-
const captureHook = {
|
|
1224
|
-
type: "command",
|
|
1225
|
-
command: `bash ${path.join(hooksDestDir, "capture-session.sh")}`,
|
|
1226
|
-
};
|
|
1227
|
-
if (!settings.hooks.PostToolUse.some((h) => h.command && h.command.includes("capture-session.sh"))) {
|
|
1228
|
-
settings.hooks.PostToolUse.push(captureHook);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// Stop hook (session end)
|
|
1232
|
-
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
1233
|
-
const stopHook = {
|
|
1234
|
-
type: "command",
|
|
1235
|
-
command: `bash ${path.join(hooksDestDir, "session-stop.sh")}`,
|
|
1236
|
-
};
|
|
1237
|
-
if (!settings.hooks.Stop.some((h) => h.command && h.command.includes("session-stop.sh"))) {
|
|
1238
|
-
settings.hooks.Stop.push(stopHook);
|
|
1239
|
-
}
|
|
1240
1749
|
|
|
1241
|
-
|
|
1242
|
-
if (!settings.hooks.PreCompact) settings.hooks.PreCompact = [];
|
|
1243
|
-
const preCompactHook = {
|
|
1244
|
-
type: "command",
|
|
1245
|
-
command: `bash ${path.join(hooksDestDir, "pre-compact.sh")}`,
|
|
1246
|
-
};
|
|
1247
|
-
if (!settings.hooks.PreCompact.some((h) => h.command && h.command.includes("pre-compact.sh"))) {
|
|
1248
|
-
settings.hooks.PreCompact.push(preCompactHook);
|
|
1249
|
-
}
|
|
1750
|
+
registerAllCoreHooks(settings, hooksDestDir, NEXO_HOME);
|
|
1250
1751
|
|
|
1251
1752
|
const settingsDir = path.dirname(CLAUDE_SETTINGS);
|
|
1252
1753
|
fs.mkdirSync(settingsDir, { recursive: true });
|
|
1253
1754
|
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
1254
|
-
log("MCP server + hooks configured in Claude Code settings.");
|
|
1755
|
+
log("MCP server + 7 core hooks configured in Claude Code settings.");
|
|
1255
1756
|
|
|
1256
|
-
// Step 7:
|
|
1757
|
+
// Step 7: Create schedule.json (only on fresh install) and install ALL 13 processes
|
|
1257
1758
|
log("Setting up automated processes...");
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
const agents = [
|
|
1262
|
-
{
|
|
1263
|
-
name: "cognitive-decay",
|
|
1264
|
-
script: "nexo-cognitive-decay.py",
|
|
1265
|
-
hour: 3,
|
|
1266
|
-
minute: 0,
|
|
1267
|
-
},
|
|
1268
|
-
{
|
|
1269
|
-
name: "postmortem",
|
|
1270
|
-
script: "nexo-postmortem-consolidator.py",
|
|
1271
|
-
hour: 23,
|
|
1272
|
-
minute: 30,
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
name: "sleep",
|
|
1276
|
-
script: "nexo-sleep.py",
|
|
1277
|
-
hour: 4,
|
|
1278
|
-
minute: 0,
|
|
1279
|
-
},
|
|
1280
|
-
{
|
|
1281
|
-
name: "self-audit",
|
|
1282
|
-
script: "nexo-daily-self-audit.py",
|
|
1283
|
-
hour: 7,
|
|
1284
|
-
minute: 0,
|
|
1285
|
-
},
|
|
1286
|
-
{ name: "catchup", script: "nexo-catchup.py", runAtLoad: true },
|
|
1287
|
-
];
|
|
1288
|
-
|
|
1289
|
-
agents.forEach((agent) => {
|
|
1290
|
-
const plistName = `com.nexo.${agent.name}.plist`;
|
|
1291
|
-
const plistPath = path.join(LAUNCH_AGENTS, plistName);
|
|
1292
|
-
|
|
1293
|
-
let scheduleBlock = "";
|
|
1294
|
-
if (agent.runAtLoad) {
|
|
1295
|
-
scheduleBlock = ` <key>RunAtLoad</key>
|
|
1296
|
-
<true/>`;
|
|
1297
|
-
} else {
|
|
1298
|
-
scheduleBlock = ` <key>StartCalendarInterval</key>
|
|
1299
|
-
<dict>
|
|
1300
|
-
<key>Hour</key>
|
|
1301
|
-
<integer>${agent.hour}</integer>
|
|
1302
|
-
<key>Minute</key>
|
|
1303
|
-
<integer>${agent.minute}</integer>
|
|
1304
|
-
</dict>
|
|
1305
|
-
<key>RunAtLoad</key>
|
|
1306
|
-
<false/>`;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1310
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1311
|
-
<plist version="1.0">
|
|
1312
|
-
<dict>
|
|
1313
|
-
<key>Label</key>
|
|
1314
|
-
<string>com.nexo.${agent.name}</string>
|
|
1315
|
-
<key>ProgramArguments</key>
|
|
1316
|
-
<array>
|
|
1317
|
-
<string>${python}</string>
|
|
1318
|
-
<string>${path.join(NEXO_HOME, "scripts", agent.script)}</string>
|
|
1319
|
-
</array>
|
|
1320
|
-
${scheduleBlock}
|
|
1321
|
-
<key>StandardOutPath</key>
|
|
1322
|
-
<string>${path.join(NEXO_HOME, "logs", `${agent.name}-stdout.log`)}</string>
|
|
1323
|
-
<key>StandardErrorPath</key>
|
|
1324
|
-
<string>${path.join(NEXO_HOME, "logs", `${agent.name}-stderr.log`)}</string>
|
|
1325
|
-
<key>EnvironmentVariables</key>
|
|
1326
|
-
<dict>
|
|
1327
|
-
<key>HOME</key>
|
|
1328
|
-
<string>${require("os").homedir()}</string>
|
|
1329
|
-
<key>NEXO_HOME</key>
|
|
1330
|
-
<string>${NEXO_HOME}</string>
|
|
1331
|
-
<key>PATH</key>
|
|
1332
|
-
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
1333
|
-
</dict>
|
|
1334
|
-
</dict>
|
|
1335
|
-
</plist>`;
|
|
1336
|
-
|
|
1337
|
-
fs.writeFileSync(plistPath, plist);
|
|
1338
|
-
// Register the agent
|
|
1339
|
-
try {
|
|
1340
|
-
execSync(
|
|
1341
|
-
`launchctl bootout gui/$(id -u) "${plistPath}" 2>/dev/null; launchctl bootstrap gui/$(id -u) "${plistPath}"`,
|
|
1342
|
-
{ stdio: "pipe" }
|
|
1343
|
-
);
|
|
1344
|
-
} catch {
|
|
1345
|
-
// May fail if not previously loaded, that's OK
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
log(`${agents.length} automated processes configured.`);
|
|
1349
|
-
|
|
1350
|
-
// Caffeinate: keep Mac awake for nocturnal processes
|
|
1351
|
-
if (doCaffeinate) {
|
|
1352
|
-
const caffHookSrc = path.join(__dirname, "..", "src", "hooks", "caffeinate-guard.sh");
|
|
1353
|
-
const caffHookDest = path.join(NEXO_HOME, "hooks", "caffeinate-guard.sh");
|
|
1354
|
-
if (fs.existsSync(caffHookSrc)) {
|
|
1355
|
-
fs.copyFileSync(caffHookSrc, caffHookDest);
|
|
1356
|
-
fs.chmodSync(caffHookDest, "755");
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
const caffPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1360
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1361
|
-
<plist version="1.0">
|
|
1362
|
-
<dict>
|
|
1363
|
-
<key>Label</key>
|
|
1364
|
-
<string>com.nexo.caffeinate</string>
|
|
1365
|
-
<key>ProgramArguments</key>
|
|
1366
|
-
<array>
|
|
1367
|
-
<string>/bin/bash</string>
|
|
1368
|
-
<string>${caffHookDest}</string>
|
|
1369
|
-
</array>
|
|
1370
|
-
<key>RunAtLoad</key>
|
|
1371
|
-
<true/>
|
|
1372
|
-
<key>KeepAlive</key>
|
|
1373
|
-
<true/>
|
|
1374
|
-
<key>StandardOutPath</key>
|
|
1375
|
-
<string>${path.join(NEXO_HOME, "logs", "caffeinate-stdout.log")}</string>
|
|
1376
|
-
<key>StandardErrorPath</key>
|
|
1377
|
-
<string>${path.join(NEXO_HOME, "logs", "caffeinate-stderr.log")}</string>
|
|
1378
|
-
</dict>
|
|
1379
|
-
</plist>`;
|
|
1759
|
+
const schedule = loadOrCreateSchedule(NEXO_HOME);
|
|
1760
|
+
installAllProcesses(platform, python, NEXO_HOME, schedule, LAUNCH_AGENTS);
|
|
1380
1761
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
try {
|
|
1384
|
-
execSync(
|
|
1385
|
-
`launchctl bootout gui/$(id -u) "${caffPlistPath}" 2>/dev/null; launchctl bootstrap gui/$(id -u) "${caffPlistPath}"`,
|
|
1386
|
-
{ stdio: "pipe" }
|
|
1387
|
-
);
|
|
1388
|
-
} catch {}
|
|
1389
|
-
log("Caffeinate enabled — Mac will stay awake for cognitive processes.");
|
|
1390
|
-
}
|
|
1391
|
-
} else {
|
|
1392
|
-
log("Non-macOS platform: background tasks will run via catch-up on startup.");
|
|
1393
|
-
log(" No OS scheduler configured — NEXO runs maintenance when MCP starts.");
|
|
1394
|
-
}
|
|
1762
|
+
// Note: prevent-sleep and tcc-approve are now part of ALL_PROCESSES
|
|
1763
|
+
// and installed by installAllProcesses() above. No separate caffeinate block needed.
|
|
1395
1764
|
|
|
1396
1765
|
// Step 8: Create shell alias so user can just type the operator's name
|
|
1397
1766
|
log("Creating shell alias...");
|
|
@@ -1449,6 +1818,15 @@ See ~/.nexo/ for configuration.
|
|
|
1449
1818
|
);
|
|
1450
1819
|
}
|
|
1451
1820
|
|
|
1821
|
+
// Write initial CLAUDE.md version tracker
|
|
1822
|
+
const claudeMdVersionMatch = claudeMd.match(/nexo-claude-md-version:\s*([\d.]+)/);
|
|
1823
|
+
if (claudeMdVersionMatch) {
|
|
1824
|
+
const dataDir = path.join(NEXO_HOME, "data");
|
|
1825
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
1826
|
+
fs.writeFileSync(path.join(dataDir, "claude_md_version.txt"), claudeMdVersionMatch[1]);
|
|
1827
|
+
log(`CLAUDE.md version tracker initialized: v${claudeMdVersionMatch[1]}`);
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1452
1830
|
console.log("");
|
|
1453
1831
|
const readyMsg = t.ready(operatorName, aliasName);
|
|
1454
1832
|
const readySub = t.readySubtext;
|