nexo-brain 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +65 -2
  2. package/bin/nexo-brain.js +208 -11
  3. package/bin/nexo.js +55 -0
  4. package/community/skills/.gitkeep +1 -0
  5. package/package.json +5 -2
  6. package/src/auto_update.py +158 -8
  7. package/src/cli.py +605 -0
  8. package/src/cognitive/_ingest.py +1 -1
  9. package/src/cognitive/_memory.py +4 -4
  10. package/src/crons/manifest.json +8 -0
  11. package/src/dashboard/app.py +700 -35
  12. package/src/dashboard/templates/adaptive.html +112 -218
  13. package/src/dashboard/templates/artifacts.html +133 -0
  14. package/src/dashboard/templates/backups.html +136 -0
  15. package/src/dashboard/templates/base.html +413 -0
  16. package/src/dashboard/templates/calendar.html +523 -654
  17. package/src/dashboard/templates/chat.html +356 -0
  18. package/src/dashboard/templates/claims.html +259 -0
  19. package/src/dashboard/templates/cortex.html +262 -0
  20. package/src/dashboard/templates/credentials.html +128 -0
  21. package/src/dashboard/templates/crons.html +370 -0
  22. package/src/dashboard/templates/dashboard.html +383 -578
  23. package/src/dashboard/templates/dreams.html +252 -0
  24. package/src/dashboard/templates/email.html +160 -0
  25. package/src/dashboard/templates/evolution.html +189 -0
  26. package/src/dashboard/templates/feed.html +249 -0
  27. package/src/dashboard/templates/followup_health.html +170 -0
  28. package/src/dashboard/templates/graph.html +191 -269
  29. package/src/dashboard/templates/guard.html +259 -0
  30. package/src/dashboard/templates/inbox.html +220 -346
  31. package/src/dashboard/templates/memory.html +317 -197
  32. package/src/dashboard/templates/operations.html +521 -698
  33. package/src/dashboard/templates/plugins.html +185 -0
  34. package/src/dashboard/templates/rules.html +246 -0
  35. package/src/dashboard/templates/sentiment.html +247 -0
  36. package/src/dashboard/templates/sessions.html +215 -182
  37. package/src/dashboard/templates/skills.html +329 -0
  38. package/src/dashboard/templates/somatic.html +68 -172
  39. package/src/dashboard/templates/triggers.html +133 -0
  40. package/src/dashboard/templates/trust.html +360 -0
  41. package/src/db/__init__.py +5 -0
  42. package/src/db/_schema.py +16 -1
  43. package/src/db/_sessions.py +22 -0
  44. package/src/db/_skills.py +980 -274
  45. package/src/doctor/__init__.py +1 -0
  46. package/src/doctor/formatters.py +52 -0
  47. package/src/doctor/models.py +44 -0
  48. package/src/doctor/orchestrator.py +42 -0
  49. package/src/doctor/providers/__init__.py +1 -0
  50. package/src/doctor/providers/boot.py +206 -0
  51. package/src/doctor/providers/deep.py +292 -0
  52. package/src/doctor/providers/runtime.py +686 -0
  53. package/src/hooks/post-compact.sh +5 -1
  54. package/src/hooks/pre-compact.sh +1 -1
  55. package/src/plugins/doctor.py +36 -0
  56. package/src/plugins/evolution.py +2 -1
  57. package/src/plugins/skills.py +135 -175
  58. package/src/requirements.txt +1 -0
  59. package/src/script_registry.py +322 -0
  60. package/src/scripts/deep-sleep/apply_findings.py +63 -48
  61. package/src/scripts/deep-sleep/extract-prompt.md +14 -0
  62. package/src/scripts/deep-sleep/synthesize-prompt.md +36 -0
  63. package/src/scripts/deep-sleep/synthesize.py +37 -1
  64. package/src/scripts/nexo-dashboard.sh +29 -0
  65. package/src/scripts/nexo-day-orchestrator.sh +139 -0
  66. package/src/scripts/nexo-evolution-run.py +2 -1
  67. package/src/scripts/nexo-learning-housekeep.py +1 -1
  68. package/src/scripts/nexo-watchdog.sh +1 -1
  69. package/src/server.py +9 -5
  70. package/src/skills/run-runtime-doctor/guide.md +12 -0
  71. package/src/skills/run-runtime-doctor/script.py +21 -0
  72. package/src/skills/run-runtime-doctor/skill.json +25 -0
  73. package/src/skills_runtime.py +347 -0
  74. package/src/tools_menu.py +3 -2
  75. package/src/tools_sessions.py +126 -0
  76. package/src/user_context.py +46 -0
  77. package/templates/nexo_helper.py +45 -0
  78. package/templates/script-template.py +44 -0
  79. package/templates/skill-script-template.py +39 -0
  80. package/templates/skill-template.md +33 -0
package/README.md CHANGED
@@ -341,7 +341,7 @@ Deep Sleep generates a `session-tone.json` that tells NEXO how to behave next mo
341
341
 
342
342
  This is read by `nexo_smart_startup` and injected into every session's context. NEXO adapts its personality based on real behavioral data, not just configuration.
343
343
 
344
- ## Cron Manifest (v2.1.0)
344
+ ## Cron Manifest & Scheduler (v2.4.0)
345
345
 
346
346
  All core crons are defined in `src/crons/manifest.json`. When you run `nexo_update`, the sync script:
347
347
  - **Installs** new crons from the manifest
@@ -349,7 +349,23 @@ All core crons are defined in `src/crons/manifest.json`. When you run `nexo_upda
349
349
  - **Removes** crons no longer in the manifest (only core ones)
350
350
  - **Never touches** personal crons you created yourself
351
351
 
352
- Run `python3 src/crons/sync.py --dry-run` to preview changes without applying.
352
+ Every cron execution is tracked in the `cron_runs` table via a universal wrapper. Use `nexo_schedule_status` to see what ran overnight:
353
+
354
+ ```
355
+ ✅ deep-sleep: 1/1 OK, 4523s avg — 37 sessions, 259 findings
356
+ ✅ immune: 48/48 OK, 2s avg
357
+ ❌ evolution: 0/1 OK — CLI timeout
358
+ ```
359
+
360
+ Add personal crons from conversation with `nexo_schedule_add` — generates LaunchAgent (macOS) or systemd timer (Linux) automatically.
361
+
362
+ ## Skill Auto-Creation (v2.4.0)
363
+
364
+ Deep Sleep automatically extracts reusable procedures from successful multi-step tasks and stores them as skills with full procedural content (steps, gotchas, markdown).
365
+
366
+ Pipeline: `trace → draft → published → archived`. Trust rises with successful use, decays without it. No human approval gates.
367
+
368
+ 7 MCP tools: `nexo_skill_create`, `nexo_skill_match`, `nexo_skill_get`, `nexo_skill_result`, `nexo_skill_list`, `nexo_skill_merge`, `nexo_skill_stats`.
353
369
 
354
370
  ## Dashboard (v1.6.0)
355
371
 
@@ -528,6 +544,38 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
528
544
  | Auto-diary | 3-layer system: PostToolUse every 10 calls, PreCompact emergency, heartbeat DIARY_OVERDUE | Built into hooks |
529
545
  | Claude Code config | MCP server + 7 hooks + 15 processes registered | ~/.claude/settings.json |
530
546
 
547
+ ### Runtime CLI
548
+
549
+ After installation or auto-update, NEXO adds `NEXO_HOME/bin` to your shell `PATH`. Open a new terminal and the `nexo` command provides operational tools:
550
+
551
+ ```bash
552
+ # Personal Scripts
553
+ nexo scripts list # List your personal scripts
554
+ nexo scripts run my-script # Run a script with injected NEXO env
555
+ nexo scripts doctor # Validate all personal scripts
556
+ nexo scripts call nexo_learning_search --input '{"query":"cron"}' # Call any MCP tool
557
+
558
+ # Skills v2
559
+ nexo skills sync # Sync filesystem skill definitions into SQLite
560
+ nexo skills list # List published/stable skills
561
+ nexo skills get SK-... # Inspect a skill definition
562
+ nexo skills apply SK-... --dry-run --json # Resolve guide/execute/hybrid without running it
563
+ nexo skills approve SK-... --execution-level local --approved-by Francisco # Optional metadata override
564
+ nexo skills evolution # Show text→script and improvement candidates
565
+
566
+ # Unified Doctor
567
+ nexo doctor # Quick boot diagnostics
568
+ nexo doctor --tier all # Full system check (boot + runtime + deep)
569
+ nexo doctor --tier runtime --json # Machine-readable health report
570
+ nexo doctor --fix # Apply deterministic repairs
571
+ ```
572
+
573
+ Personal scripts live in `NEXO_HOME/scripts/` with inline metadata. See `docs/writing-scripts.md` for details.
574
+
575
+ Skills v2 combine procedural guides with optional executable scripts. Personal skills live in `NEXO_HOME/skills/`, packaged core skills live in `NEXO_CODE/skills/` during development and `NEXO_HOME/skills-core/` in installed environments, and staged runtime copies live in `NEXO_HOME/skills-runtime/`. Execution is fully autonomous: Deep Sleep can evolve mature guide skills into executable drafts automatically, and runtime execution no longer waits for manual approval. See `docs/skills-v2.md` for the full model.
576
+
577
+ The Doctor system reads existing health artifacts (immune, watchdog, self-audit) without triggering repairs in default mode.
578
+
531
579
  ### Requirements
532
580
 
533
581
  - **macOS or Linux** (Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))
@@ -741,6 +789,21 @@ If NEXO Brain is useful to you, consider:
741
789
 
742
790
  ## Changelog
743
791
 
792
+ ### v2.4.0 — Skills, Cron Scheduler, Security, Full Audit (2026-04-03)
793
+ - **Skill Auto-Creation**: Deep Sleep extracts reusable procedures from sessions. Content stored as markdown with steps and gotchas. Trust pipeline with autonomous quality control.
794
+ - **Cron Scheduler**: execution tracking (`cron_runs` table), `nexo_schedule_status` and `nexo_schedule_add` MCP tools, universal cron wrapper for all processes.
795
+ - **Deep Sleep v2.4**: watermark-based collection (late-night sessions included), per-session checkpointing (crash-safe), retry x3, JSON parsing fix, auto-calibration of personality settings.
796
+ - **Security**: credential redaction in tool logs, transcript sanitization, command injection fix in dashboard, path traversal protection in plugin loader.
797
+ - **Diary filter**: startup only shows human sessions, auto-closed cron sessions filtered out. Email sessions preserved as real interactions.
798
+ - **Preflight CI**: 66 automated checks (py_compile, bash -n, manifest consistency, npm artifact, forbidden markers).
799
+ - **Python 3.9 compat**: `from __future__ import annotations` across 18 files.
800
+ - **Linux**: full systemd timer support, .bashrc alias for interactive shells.
801
+ - Passed 5-phase automated audit: Product, Failure, Security, Packaging, UX.
802
+
803
+ ### v2.2.0 — Trust Score v2 (2026-04-01)
804
+ - **Trust Score**: fair daily calibration from Deep Sleep analysis. Score 0-100 based on corrections, autonomy, proactivity.
805
+ - **Cognitive Quarantine**: new memories go through quarantine before promotion to LTM.
806
+
744
807
  ### v2.0.0 — Unified Architecture (2026-03-31)
745
808
  - **Code/data separation**: Code in repo (`src/`), personal data in `NEXO_HOME` (default `~/.nexo/`). `NEXO_HOME` env var required.
746
809
  - **Plugin loader dual-directory**: Scans `src/plugins/` (base) then `NEXO_HOME/plugins/` (personal override by filename).
package/bin/nexo-brain.js CHANGED
@@ -31,6 +31,13 @@ const LAUNCH_AGENTS = path.join(
31
31
  "LaunchAgents"
32
32
  );
33
33
 
34
+ function isEphemeralInstall(nexoHome) {
35
+ const homeDir = require("os").homedir();
36
+ const allowEphemeral = process.env.NEXO_ALLOW_EPHEMERAL_INSTALL === "1";
37
+ if (allowEphemeral) return false;
38
+ return nexoHome.startsWith("/tmp/") || homeDir.startsWith("/tmp/");
39
+ }
40
+
34
41
  const rl = readline.createInterface({
35
42
  input: process.stdin,
36
43
  output: process.stdout,
@@ -66,9 +73,9 @@ const ALL_PROCESSES = [
66
73
  // --- Every 5 minutes ---
67
74
  { name: "auto-close-sessions", script: "auto_close_sessions.py", interpreter: "python", scriptDir: "root",
68
75
  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
76
  // --- Every 30 minutes ---
77
+ { name: "watchdog", script: "nexo-watchdog.sh", interpreter: "bash", scriptDir: "scripts",
78
+ type: "interval", intervalMinutes: 30, purpose: "Health monitoring" },
72
79
  { name: "immune", script: "nexo-immune.py", interpreter: "python", scriptDir: "scripts",
73
80
  type: "interval", intervalMinutes: 30, purpose: "System immunity checks" },
74
81
  // --- Every 2 hours ---
@@ -86,6 +93,10 @@ const ALL_PROCESSES = [
86
93
  // --- KeepAlive (persistent daemon) ---
87
94
  { name: "prevent-sleep", script: "nexo-prevent-sleep.sh", interpreter: "bash", scriptDir: "scripts",
88
95
  type: "keepAlive", purpose: "Keep machine awake for nocturnal processes" },
96
+ { name: "dashboard", script: "nexo-dashboard.sh", interpreter: "bash", scriptDir: "scripts",
97
+ type: "keepAlive", optional: "dashboard", purpose: "Web dashboard at localhost:6174" },
98
+ { name: "day-orchestrator", script: "nexo-day-orchestrator.sh", interpreter: "bash", scriptDir: "scripts",
99
+ type: "keepAlive", optional: "orchestrator", purpose: "Autonomous NEXO cycles every 15 min (8:00-23:00)" },
89
100
  // --- Daily (times from schedule.json) ---
90
101
  { name: "cognitive-decay", script: "nexo-cognitive-decay.py", interpreter: "python", scriptDir: "scripts",
91
102
  type: "daily", defaultHour: 3, defaultMinute: 0, purpose: "Memory decay" },
@@ -262,7 +273,7 @@ const DAY_MAP = {
262
273
  * Linux+systemd: .service + .timer files
263
274
  * Linux fallback: crontab entries
264
275
  */
265
- function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir) {
276
+ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir, enabledOptionals = {}) {
266
277
  const home = require("os").homedir();
267
278
  const nexoCode = nexoHome;
268
279
  const logsDir = path.join(nexoHome, "logs");
@@ -298,6 +309,8 @@ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAge
298
309
  for (const proc of ALL_PROCESSES) {
299
310
  // Skip macOnly processes on Linux
300
311
  if (proc.macOnly && platform !== "darwin") continue;
312
+ // Skip optional processes that weren't enabled
313
+ if (proc.optional && !enabledOptionals[proc.optional]) continue;
301
314
 
302
315
  const plistName = `com.nexo.${proc.name}.plist`;
303
316
  const plistPath = path.join(launchAgentsDir, plistName);
@@ -404,6 +417,7 @@ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAge
404
417
 
405
418
  for (const proc of ALL_PROCESSES) {
406
419
  if (proc.macOnly) continue; // tcc-approve is macOS only
420
+ if (proc.optional && !enabledOptionals[proc.optional]) continue;
407
421
  const serviceName = `nexo-${proc.name}`;
408
422
  const serviceFile = path.join(systemdDir, `${serviceName}.service`);
409
423
  const timerFile = path.join(systemdDir, `${serviceName}.timer`);
@@ -481,6 +495,7 @@ WantedBy=timers.target
481
495
  const envLine2 = `NEXO_CODE=${nexoCode}`;
482
496
 
483
497
  for (const proc of ALL_PROCESSES) {
498
+ if (proc.optional && !enabledOptionals[proc.optional]) continue;
484
499
  const sPath = scriptPath(proc);
485
500
  const interp = interpreterPath(proc);
486
501
  const s = getSchedule(proc);
@@ -729,8 +744,13 @@ async function main() {
729
744
  // Regenerate ALL 13 LaunchAgents / systemd timers
730
745
  const migSchedule = loadOrCreateSchedule(NEXO_HOME);
731
746
  const migPython = findVenvPython(NEXO_HOME) || "python3";
732
- installAllProcesses(platform, migPython, NEXO_HOME, migSchedule, LAUNCH_AGENTS);
733
- log(" All 13 automated processes updated.");
747
+ let migOptionals = {};
748
+ try {
749
+ const optFile = path.join(NEXO_HOME, "config", "optionals.json");
750
+ if (fs.existsSync(optFile)) migOptionals = JSON.parse(fs.readFileSync(optFile, "utf8"));
751
+ } catch {}
752
+ installAllProcesses(platform, migPython, NEXO_HOME, migSchedule, LAUNCH_AGENTS, migOptionals);
753
+ log(" All automated processes updated.");
734
754
 
735
755
  // Update version file
736
756
  fs.writeFileSync(versionFile, JSON.stringify({
@@ -776,7 +796,7 @@ async function main() {
776
796
  // Same version — backfill crons/ if missing (for installs before crons was shipped)
777
797
  const cronsDest = path.join(NEXO_HOME, "crons");
778
798
  const cronsSrc = path.join(__dirname, "..", "src", "crons");
779
- if (!fs.existsSync(path.join(cronsDest, "manifest.json")) && fs.existsSync(cronsSrc)) {
799
+ if (fs.existsSync(cronsSrc)) {
780
800
  const copyDirRec2 = (src, dest) => {
781
801
  fs.mkdirSync(dest, { recursive: true });
782
802
  fs.readdirSync(src).forEach(item => {
@@ -788,7 +808,61 @@ async function main() {
788
808
  });
789
809
  };
790
810
  copyDirRec2(cronsSrc, cronsDest);
791
- log("Backfilled crons/ directory (catchup & watchdog need it).");
811
+ log("Refreshed crons/ directory.");
812
+
813
+ const cronSyncPath = path.join(cronsSrc, "sync.py");
814
+ const syncPython = findVenvPython(NEXO_HOME) || run("which python3") || "python3";
815
+ if (fs.existsSync(cronSyncPath)) {
816
+ const syncResult = spawnSync(syncPython, [cronSyncPath], {
817
+ env: { ...process.env, NEXO_HOME, NEXO_CODE: path.join(__dirname, "..", "src") },
818
+ stdio: "pipe",
819
+ encoding: "utf8",
820
+ });
821
+ if (syncResult.status === 0) {
822
+ log("Core crons reconciled with manifest.");
823
+ } else {
824
+ const syncErr = (syncResult.stderr || syncResult.stdout || "").trim();
825
+ log(`Cron sync warning: ${syncErr || `exit ${syncResult.status}`}`);
826
+ }
827
+ }
828
+ }
829
+
830
+ // Same version — refresh packaged core skills/templates/runtime helpers too.
831
+ const skillsCoreDest = path.join(NEXO_HOME, "skills-core");
832
+ const skillsCoreSrc = path.join(__dirname, "..", "src", "skills");
833
+ if (fs.existsSync(skillsCoreSrc)) {
834
+ const copyDirRec3 = (src, dest) => {
835
+ fs.mkdirSync(dest, { recursive: true });
836
+ fs.readdirSync(src).forEach(item => {
837
+ if (item === "__pycache__" || item.endsWith(".pyc")) return;
838
+ const srcP = path.join(src, item);
839
+ const destP = path.join(dest, item);
840
+ if (fs.statSync(srcP).isDirectory()) copyDirRec3(srcP, destP);
841
+ else fs.copyFileSync(srcP, destP);
842
+ });
843
+ };
844
+ copyDirRec3(skillsCoreSrc, skillsCoreDest);
845
+ log("Refreshed skills-core/ directory.");
846
+ }
847
+
848
+ ["skills_runtime.py"].forEach((fname) => {
849
+ const srcFile = path.join(__dirname, "..", "src", fname);
850
+ const destFile = path.join(NEXO_HOME, fname);
851
+ if (fs.existsSync(srcFile)) {
852
+ fs.copyFileSync(srcFile, destFile);
853
+ }
854
+ });
855
+
856
+ const templatesSrc = path.join(__dirname, "..", "templates");
857
+ const templatesDest = path.join(NEXO_HOME, "templates");
858
+ if (fs.existsSync(templatesSrc)) {
859
+ fs.mkdirSync(templatesDest, { recursive: true });
860
+ ["script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"].forEach((f) => {
861
+ const src = path.join(templatesSrc, f);
862
+ if (fs.existsSync(src)) {
863
+ fs.copyFileSync(src, path.join(templatesDest, f));
864
+ }
865
+ });
792
866
  }
793
867
 
794
868
  log(`Already at v${currentVersion}. No migration needed.`);
@@ -895,6 +969,12 @@ async function main() {
895
969
  caffeinateQ: " Keep Mac awake for my cognitive processes at night?\n (I consolidate memory, clean duplicates, and discover connections while you sleep)\n 1. Yes\n 2. No\n > ",
896
970
  caffYes: "Nocturnal processes scheduled.",
897
971
  caffNo: "Ok, I'll run them when I can.",
972
+ dashboardQ: " Enable web dashboard at localhost:6174?\n (Always-on UI to explore memory, sessions, learnings, and system health)\n 1. Yes\n 2. No\n > ",
973
+ dashYes: "Dashboard enabled.",
974
+ dashNo: "Dashboard disabled. You can start it manually: nexo dashboard",
975
+ orchestratorQ: " Enable autonomous mode? (I'll work on my own every 15 min: check followups, emails, infra, and report by email)\n 1. Yes\n 2. No\n > ",
976
+ orchYes: "Autonomous mode enabled. I'll be working for you 8:00-23:00.",
977
+ orchNo: "Autonomous mode disabled. I'll only work when you open a session.",
898
978
  autoInstallQ: " Can I install tools automatically if I need them? (brew, pip, npm)\n 1. Yes, install whatever you need\n 2. Ask me before installing anything\n > ",
899
979
  autoInstallYes: "Auto-install enabled.",
900
980
  autoInstallNo: "I'll ask before installing.",
@@ -924,6 +1004,12 @@ async function main() {
924
1004
  caffeinateQ: " ¿Mantengo el Mac despierto para mis procesos cognitivos nocturnos?\n (Consolido memoria, limpio duplicados y descubro conexiones mientras duermes)\n 1. Sí\n 2. No\n > ",
925
1005
  caffYes: "Procesos nocturnos programados.",
926
1006
  caffNo: "Ok, los ejecutaré cuando pueda.",
1007
+ dashboardQ: " ¿Activar el dashboard web en localhost:6174?\n (UI siempre activa para explorar memoria, sesiones, learnings y salud del sistema)\n 1. Sí\n 2. No\n > ",
1008
+ dashYes: "Dashboard activado.",
1009
+ dashNo: "Dashboard desactivado. Puedes iniciarlo manualmente: nexo dashboard",
1010
+ orchestratorQ: " ¿Activar modo autónomo? (Trabajo solo cada 15 min: reviso followups, emails, infra, y te informo por email)\n 1. Sí\n 2. No\n > ",
1011
+ orchYes: "Modo autónomo activado. Estaré trabajando para ti de 8:00 a 23:00.",
1012
+ orchNo: "Modo autónomo desactivado. Solo trabajo cuando abras sesión.",
927
1013
  autoInstallQ: " ¿Puedo instalar herramientas automáticamente si las necesito? (brew, pip, npm)\n 1. Sí, instala lo que necesites\n 2. Pregúntame antes de instalar algo\n > ",
928
1014
  autoInstallYes: "Auto-instalación activada.",
929
1015
  autoInstallNo: "Te preguntaré antes.",
@@ -953,6 +1039,12 @@ async function main() {
953
1039
  caffeinateQ: " Garder le Mac éveillé pour mes processus nocturnes ?\n 1. Oui\n 2. Non\n > ",
954
1040
  caffYes: "Processus nocturnes programmés.",
955
1041
  caffNo: "Ok, je les exécuterai quand possible.",
1042
+ dashboardQ: " Activer le dashboard web sur localhost:6174 ?\n (UI toujours active pour explorer mémoire, sessions et santé du système)\n 1. Oui\n 2. Non\n > ",
1043
+ dashYes: "Dashboard activé.",
1044
+ dashNo: "Dashboard désactivé. Démarrage manuel : nexo dashboard",
1045
+ orchestratorQ: " Activer le mode autonome ? (Je travaille seul toutes les 15 min : followups, emails, infra, rapport par email)\n 1. Oui\n 2. Non\n > ",
1046
+ orchYes: "Mode autonome activé. Je travaille pour vous de 8h à 23h.",
1047
+ orchNo: "Mode autonome désactivé. Je travaille uniquement en session.",
956
1048
  autoInstallQ: " Puis-je installer des outils automatiquement ? (brew, pip, npm)\n 1. Oui\n 2. Demande-moi avant\n > ",
957
1049
  autoInstallYes: "Auto-installation activée.",
958
1050
  autoInstallNo: "Je demanderai avant.",
@@ -982,6 +1074,12 @@ async function main() {
982
1074
  caffeinateQ: " Mac wach halten für nächtliche Prozesse?\n 1. Ja\n 2. Nein\n > ",
983
1075
  caffYes: "Nachtprozesse geplant.",
984
1076
  caffNo: "Ok, führe sie aus wenn möglich.",
1077
+ dashboardQ: " Web-Dashboard auf localhost:6174 aktivieren?\n (Immer aktive UI für Speicher, Sitzungen und Systemgesundheit)\n 1. Ja\n 2. Nein\n > ",
1078
+ dashYes: "Dashboard aktiviert.",
1079
+ dashNo: "Dashboard deaktiviert. Manuell starten: nexo dashboard",
1080
+ orchestratorQ: " Autonomen Modus aktivieren? (Ich arbeite alle 15 Min selbstständig: Followups, E-Mails, Infra, Bericht per E-Mail)\n 1. Ja\n 2. Nein\n > ",
1081
+ orchYes: "Autonomer Modus aktiviert. Ich arbeite für dich von 8:00 bis 23:00.",
1082
+ orchNo: "Autonomer Modus deaktiviert. Ich arbeite nur in Sitzungen.",
985
1083
  autoInstallQ: " Darf ich Tools automatisch installieren? (brew, pip, npm)\n 1. Ja\n 2. Frag mich vorher\n > ",
986
1084
  autoInstallYes: "Auto-Installation aktiviert.",
987
1085
  autoInstallNo: "Frage vorher.",
@@ -1011,6 +1109,12 @@ async function main() {
1011
1109
  caffeinateQ: " Tenere il Mac sveglio per i processi notturni?\n 1. Sì\n 2. No\n > ",
1012
1110
  caffYes: "Processi notturni programmati.",
1013
1111
  caffNo: "Ok, li eseguirò quando possibile.",
1112
+ dashboardQ: " Attivare la dashboard web su localhost:6174?\n (UI sempre attiva per esplorare memoria, sessioni e salute del sistema)\n 1. Sì\n 2. No\n > ",
1113
+ dashYes: "Dashboard attivata.",
1114
+ dashNo: "Dashboard disattivata. Avvio manuale: nexo dashboard",
1115
+ orchestratorQ: " Attivare la modalità autonoma? (Lavoro da solo ogni 15 min: followup, email, infra, report via email)\n 1. Sì\n 2. No\n > ",
1116
+ orchYes: "Modalità autonoma attivata. Lavoro per te dalle 8:00 alle 23:00.",
1117
+ orchNo: "Modalità autonoma disattivata. Lavoro solo nelle sessioni.",
1014
1118
  autoInstallQ: " Posso installare strumenti automaticamente? (brew, pip, npm)\n 1. Sì\n 2. Chiedimi prima\n > ",
1015
1119
  autoInstallYes: "Auto-installazione attivata.",
1016
1120
  autoInstallNo: "Chiederò prima.",
@@ -1040,6 +1144,12 @@ async function main() {
1040
1144
  caffeinateQ: " Manter o Mac acordado para processos noturnos?\n 1. Sim\n 2. Não\n > ",
1041
1145
  caffYes: "Processos noturnos agendados.",
1042
1146
  caffNo: "Ok, executo quando possível.",
1147
+ dashboardQ: " Ativar dashboard web em localhost:6174?\n (UI sempre ativa para explorar memória, sessões e saúde do sistema)\n 1. Sim\n 2. Não\n > ",
1148
+ dashYes: "Dashboard ativado.",
1149
+ dashNo: "Dashboard desativado. Iniciar manualmente: nexo dashboard",
1150
+ orchestratorQ: " Ativar modo autônomo? (Trabalho sozinho a cada 15 min: followups, emails, infra, relatório por email)\n 1. Sim\n 2. Não\n > ",
1151
+ orchYes: "Modo autônomo ativado. Trabalho para você das 8:00 às 23:00.",
1152
+ orchNo: "Modo autônomo desativado. Trabalho apenas nas sessões.",
1043
1153
  autoInstallQ: " Posso instalar ferramentas automaticamente? (brew, pip, npm)\n 1. Sim\n 2. Pergunta antes\n > ",
1044
1154
  autoInstallYes: "Auto-instalação ativada.",
1045
1155
  autoInstallNo: "Perguntarei antes.",
@@ -1158,6 +1268,8 @@ async function main() {
1158
1268
  // Step 5: Deep scan (P9)
1159
1269
  let doScan = false;
1160
1270
  let doCaffeinate = false;
1271
+ let doDashboard = false;
1272
+ let doOrchestrator = false;
1161
1273
  let autoInstall = "ask";
1162
1274
  if (!useDefaults) {
1163
1275
  const scanAnswer = await ask(t.scanQ);
@@ -1172,6 +1284,18 @@ async function main() {
1172
1284
  console.log("");
1173
1285
  }
1174
1286
 
1287
+ // Step 6b: Dashboard — always-on web UI
1288
+ const dashAnswer = await ask(t.dashboardQ);
1289
+ doDashboard = dashAnswer.trim() === "1" || dashAnswer.trim().toLowerCase().startsWith("y") || dashAnswer.trim().toLowerCase().startsWith("s");
1290
+ log(doDashboard ? `✓ ${t.dashYes}` : t.dashNo);
1291
+ console.log("");
1292
+
1293
+ // Step 6c: Day Orchestrator — autonomous NEXO cycles
1294
+ const orchAnswer = await ask(t.orchestratorQ);
1295
+ doOrchestrator = orchAnswer.trim() === "1" || orchAnswer.trim().toLowerCase().startsWith("y") || orchAnswer.trim().toLowerCase().startsWith("s");
1296
+ log(doOrchestrator ? `✓ ${t.orchYes}` : t.orchNo);
1297
+ console.log("");
1298
+
1175
1299
  // Step 7: Auto-install permission (P11)
1176
1300
  const autoInstallAnswer = await ask(t.autoInstallQ);
1177
1301
  autoInstall = (autoInstallAnswer.trim() === "1" || autoInstallAnswer.trim().toLowerCase().startsWith("y") || autoInstallAnswer.trim().toLowerCase().startsWith("s")) ? "auto" : "ask";
@@ -1224,8 +1348,12 @@ async function main() {
1224
1348
  log("Setting up NEXO home...");
1225
1349
  const dirs = [
1226
1350
  NEXO_HOME,
1351
+ path.join(NEXO_HOME, "bin"),
1227
1352
  path.join(NEXO_HOME, "plugins"),
1228
1353
  path.join(NEXO_HOME, "scripts"),
1354
+ path.join(NEXO_HOME, "skills"),
1355
+ path.join(NEXO_HOME, "skills-core"),
1356
+ path.join(NEXO_HOME, "skills-runtime"),
1229
1357
  path.join(NEXO_HOME, "logs"),
1230
1358
  path.join(NEXO_HOME, "backups"),
1231
1359
  path.join(NEXO_HOME, "coordination"),
@@ -1275,6 +1403,7 @@ async function main() {
1275
1403
  const srcDir = path.join(__dirname, "..", "src");
1276
1404
  const pluginsSrcDir = path.join(srcDir, "plugins");
1277
1405
  const scriptsSrcDir = path.join(srcDir, "scripts");
1406
+ const skillsSrcDir = path.join(srcDir, "skills");
1278
1407
  const templateDir = path.join(__dirname, "..", "templates");
1279
1408
 
1280
1409
  // Recursive copy helper (skips __pycache__, .pyc, .db files)
@@ -1315,6 +1444,9 @@ async function main() {
1315
1444
  "tools_task_history.py",
1316
1445
  "tools_menu.py",
1317
1446
  "requirements.txt",
1447
+ "cli.py",
1448
+ "script_registry.py",
1449
+ "skills_runtime.py",
1318
1450
  ];
1319
1451
  coreFiles.forEach((f) => {
1320
1452
  const src = path.join(srcDir, f);
@@ -1323,8 +1455,31 @@ async function main() {
1323
1455
  }
1324
1456
  });
1325
1457
 
1458
+ // Runtime CLI wrapper lives in NEXO_HOME/bin so it survives npx installs.
1459
+ const runtimeCli = [
1460
+ "#!/usr/bin/env bash",
1461
+ "set -euo pipefail",
1462
+ "",
1463
+ `NEXO_HOME="${NEXO_HOME}"`,
1464
+ 'PYTHON="$NEXO_HOME/.venv/bin/python3"',
1465
+ 'if [ ! -x "$PYTHON" ]; then',
1466
+ ' if command -v python3 >/dev/null 2>&1; then',
1467
+ ' PYTHON="python3"',
1468
+ " else",
1469
+ ' PYTHON="python"',
1470
+ " fi",
1471
+ "fi",
1472
+ 'export NEXO_HOME',
1473
+ 'export NEXO_CODE="$NEXO_HOME"',
1474
+ 'exec "$PYTHON" "$NEXO_HOME/cli.py" "$@"',
1475
+ "",
1476
+ ].join("\n");
1477
+ const runtimeCliPath = path.join(NEXO_HOME, "bin", "nexo");
1478
+ fs.writeFileSync(runtimeCliPath, runtimeCli);
1479
+ fs.chmodSync(runtimeCliPath, 0o755);
1480
+
1326
1481
  // Core packages (directories with __init__.py)
1327
- ["db", "cognitive"].forEach(pkg => {
1482
+ ["db", "cognitive", "doctor"].forEach(pkg => {
1328
1483
  const pkgSrc = path.join(srcDir, pkg);
1329
1484
  if (fs.existsSync(pkgSrc)) {
1330
1485
  copyDirRecursive(pkgSrc, path.join(NEXO_HOME, pkg));
@@ -1349,6 +1504,12 @@ async function main() {
1349
1504
  });
1350
1505
  }
1351
1506
 
1507
+ // Core skills are shipped separately from personal skills.
1508
+ if (fs.existsSync(skillsSrcDir)) {
1509
+ copyDirRecursive(skillsSrcDir, path.join(NEXO_HOME, "skills-core"));
1510
+ log(" Core skills installed.");
1511
+ }
1512
+
1352
1513
  // Dashboard (recursive — includes static/, templates/)
1353
1514
  const dashSrcDir = path.join(srcDir, "dashboard");
1354
1515
  if (fs.existsSync(dashSrcDir)) {
@@ -1370,6 +1531,19 @@ async function main() {
1370
1531
  log(" Crons installed.");
1371
1532
  }
1372
1533
 
1534
+ // Templates directory (scripts + skills scaffolds)
1535
+ const templatesDest = path.join(NEXO_HOME, "templates");
1536
+ fs.mkdirSync(templatesDest, { recursive: true });
1537
+ if (fs.existsSync(templateDir)) {
1538
+ ["script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"].forEach(f => {
1539
+ const src = path.join(templateDir, f);
1540
+ if (fs.existsSync(src)) {
1541
+ fs.copyFileSync(src, path.join(templatesDest, f));
1542
+ }
1543
+ });
1544
+ log(" Script and skill templates installed.");
1545
+ }
1546
+
1373
1547
  // Hooks directory
1374
1548
  const hooksSrcDir = path.join(srcDir, "hooks");
1375
1549
  if (fs.existsSync(hooksSrcDir)) {
@@ -1862,12 +2036,25 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1862
2036
  // Step 7: Create schedule.json (only on fresh install) and install ALL 13 processes
1863
2037
  log("Setting up automated processes...");
1864
2038
  const schedule = loadOrCreateSchedule(NEXO_HOME);
1865
- installAllProcesses(platform, python, NEXO_HOME, schedule, LAUNCH_AGENTS);
2039
+ const enabledOptionals = { dashboard: doDashboard, orchestrator: doOrchestrator };
2040
+ if (isEphemeralInstall(NEXO_HOME)) {
2041
+ log("Ephemeral HOME/NEXO_HOME detected — skipping LaunchAgents installation.");
2042
+ } else {
2043
+ installAllProcesses(platform, python, NEXO_HOME, schedule, LAUNCH_AGENTS, enabledOptionals);
2044
+ }
2045
+
2046
+ // Persist optional process preferences for auto-update
2047
+ try {
2048
+ const configDir = path.join(NEXO_HOME, "config");
2049
+ fs.mkdirSync(configDir, { recursive: true });
2050
+ const optFile = path.join(configDir, "optionals.json");
2051
+ fs.writeFileSync(optFile, JSON.stringify(enabledOptionals, null, 2));
2052
+ } catch {}
1866
2053
 
1867
2054
  // Note: prevent-sleep and tcc-approve are now part of ALL_PROCESSES
1868
2055
  // and installed by installAllProcesses() above. No separate caffeinate block needed.
1869
2056
 
1870
- // Step 8: Create shell alias so user can just type the operator's name
2057
+ // Step 8: Create shell alias and add runtime CLI to PATH
1871
2058
  log("Creating shell alias...");
1872
2059
  const aliasName = operatorName.toLowerCase();
1873
2060
  const savedCliPath = (() => {
@@ -1877,6 +2064,8 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1877
2064
  const claudeBin = savedCliPath || run("which claude") || "claude";
1878
2065
  const aliasLine = `alias ${aliasName}='${claudeBin} --dangerously-skip-permissions "."'`;
1879
2066
  const aliasComment = `# ${operatorName} — start Claude Code with ${operatorName} speaking first`;
2067
+ const nexoPathLine = `export PATH="${path.join(NEXO_HOME, "bin")}:$PATH"`;
2068
+ const nexoPathComment = "# NEXO runtime CLI";
1880
2069
 
1881
2070
  // Detect shell and add alias
1882
2071
  const userShell = process.env.SHELL || "/bin/bash";
@@ -1899,6 +2088,14 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1899
2088
  rcContent = fs.readFileSync(rcFile, "utf8");
1900
2089
  }
1901
2090
 
2091
+ if (!rcContent.includes(nexoPathLine)) {
2092
+ fs.appendFileSync(rcFile, `\n${nexoPathComment}\n${nexoPathLine}\n`);
2093
+ log(`Added NEXO runtime CLI to ${path.basename(rcFile)}`);
2094
+ rcContent += `\n${nexoPathComment}\n${nexoPathLine}\n`;
2095
+ } else {
2096
+ log(`Runtime CLI already present in ${path.basename(rcFile)}`);
2097
+ }
2098
+
1902
2099
  if (!rcContent.includes(`alias ${aliasName}=`)) {
1903
2100
  fs.appendFileSync(rcFile, `\n${aliasComment}\n${aliasLine}\n`);
1904
2101
  log(`Added '${aliasName}' alias to ${path.basename(rcFile)}`);
@@ -1906,7 +2103,7 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1906
2103
  log(`Alias '${aliasName}' already exists in ${path.basename(rcFile)}`);
1907
2104
  }
1908
2105
  }
1909
- log(`After setup, open a new terminal and type: ${aliasName}`);
2106
+ log(`After setup, open a new terminal and type: ${aliasName} or nexo`);
1910
2107
  console.log("");
1911
2108
 
1912
2109
  // Step 9: Generate CLAUDE.md template
package/bin/nexo.js ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * nexo — Runtime operational CLI for NEXO.
4
+ *
5
+ * Thin Node launcher that resolves NEXO_HOME, finds Python,
6
+ * and delegates to src/cli.py (repo mode) or NEXO_HOME/cli.py (installed mode).
7
+ *
8
+ * Business logic lives in Python, not here.
9
+ */
10
+ const { spawnSync } = require("child_process");
11
+ const fs = require("fs");
12
+ const os = require("os");
13
+ const path = require("path");
14
+
15
+ const NEXO_HOME = process.env.NEXO_HOME || path.join(os.homedir(), ".nexo");
16
+
17
+ function findPython() {
18
+ const candidates = [
19
+ path.join(NEXO_HOME, ".venv", "bin", "python3"),
20
+ path.join(NEXO_HOME, ".venv", "bin", "python"),
21
+ "python3",
22
+ "python",
23
+ ];
24
+ for (const c of candidates) {
25
+ if (c.includes("/") ? fs.existsSync(c) : true) return c;
26
+ }
27
+ return "python3";
28
+ }
29
+
30
+ function findCliPy() {
31
+ const repoCandidate = path.join(__dirname, "..", "src", "cli.py");
32
+ const installedCandidate = path.join(NEXO_HOME, "cli.py");
33
+ if (fs.existsSync(repoCandidate)) return repoCandidate;
34
+ return installedCandidate;
35
+ }
36
+
37
+ const python = findPython();
38
+ const cliPy = findCliPy();
39
+
40
+ if (!fs.existsSync(cliPy)) {
41
+ console.error(`NEXO CLI not found at ${cliPy}`);
42
+ console.error("Run 'nexo-brain' first to complete installation.");
43
+ process.exit(1);
44
+ }
45
+
46
+ const result = spawnSync(python, [cliPy, ...process.argv.slice(2)], {
47
+ stdio: "inherit",
48
+ env: {
49
+ ...process.env,
50
+ NEXO_HOME,
51
+ NEXO_CODE: path.join(__dirname, "..", "src"),
52
+ },
53
+ });
54
+
55
+ process.exit(result.status ?? 1);
@@ -0,0 +1 @@
1
+
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO — Cognitive co-operator for Claude Code. Memory, emotional intelligence, overnight learning (Deep Sleep), cron management, trust scoring, and adaptive calibration.",
6
6
  "bin": {
7
- "nexo-brain": "./bin/nexo-brain.js"
7
+ "nexo-brain": "./bin/nexo-brain.js",
8
+ "nexo": "./bin/nexo.js"
8
9
  },
9
10
  "keywords": [
10
11
  "claude-code",
@@ -54,8 +55,10 @@
54
55
  },
55
56
  "files": [
56
57
  "bin/nexo-brain.js",
58
+ "bin/nexo.js",
57
59
  "bin/postinstall.js",
58
60
  "src/",
61
+ "community/",
59
62
  "!src/**/__pycache__",
60
63
  "!src/**/*.pyc",
61
64
  "!src/**/*.pyo",