nexo-brain 2.4.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +80 -4
  2. package/bin/nexo-brain.js +238 -12
  3. package/bin/nexo.js +55 -0
  4. package/community/skills/.gitkeep +1 -0
  5. package/package.json +11 -3
  6. package/src/auto_update.py +193 -9
  7. package/src/cli.py +719 -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/evolution_cycle.py +86 -6
  54. package/src/hooks/post-compact.sh +5 -1
  55. package/src/hooks/pre-compact.sh +1 -1
  56. package/src/plugins/doctor.py +36 -0
  57. package/src/plugins/evolution.py +11 -3
  58. package/src/plugins/skills.py +135 -175
  59. package/src/requirements.txt +1 -0
  60. package/src/script_registry.py +322 -0
  61. package/src/scripts/deep-sleep/apply_findings.py +63 -48
  62. package/src/scripts/deep-sleep/extract-prompt.md +14 -0
  63. package/src/scripts/deep-sleep/synthesize-prompt.md +36 -0
  64. package/src/scripts/deep-sleep/synthesize.py +37 -1
  65. package/src/scripts/nexo-dashboard.sh +29 -0
  66. package/src/scripts/nexo-day-orchestrator.sh +139 -0
  67. package/src/scripts/nexo-evolution-run.py +141 -54
  68. package/src/scripts/nexo-learning-housekeep.py +1 -1
  69. package/src/scripts/nexo-watchdog.sh +1 -1
  70. package/src/server.py +9 -5
  71. package/src/skills/run-runtime-doctor/guide.md +12 -0
  72. package/src/skills/run-runtime-doctor/script.py +21 -0
  73. package/src/skills/run-runtime-doctor/skill.json +25 -0
  74. package/src/skills_runtime.py +347 -0
  75. package/src/tools_menu.py +3 -2
  76. package/src/tools_sessions.py +126 -0
  77. package/src/user_context.py +46 -0
  78. package/templates/nexo_helper.py +45 -0
  79. package/templates/script-template.py +44 -0
  80. package/templates/skill-script-template.py +39 -0
  81. 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
 
@@ -519,8 +535,12 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
519
535
  | MCP server | 147+ tools for memory, cognition, learning, guard | NEXO_HOME/ |
520
536
  | Plugins | Guard, episodic memory, cognitive memory, entities, preferences, update, etc. | Code: src/plugins/, Personal: NEXO_HOME/plugins/ |
521
537
  | Hooks (7) | SessionStart, Stop, PostToolUse, PreCompact, PostCompact | NEXO_HOME/hooks/ |
522
- | Nervous system | 15 autonomous processes (decay, sleep, audit, evolution, watchdog, etc.) | NEXO_HOME/scripts/ |
523
- | Dashboard | Web UI at localhost:6174 (6 pages) | NEXO_HOME/dashboard/ |
538
+ | Nervous system | 17 autonomous processes (decay, sleep, audit, evolution, watchdog, orchestrator, dashboard, etc.) | NEXO_HOME/scripts/ |
539
+ | Dashboard | Web UI at localhost:6174 (23 modules, dark theme) — opt-in, always-on | NEXO_HOME/dashboard/ |
540
+ | Runtime CLI | `nexo` command: scripts, doctor, skills, update | NEXO_HOME/bin/ |
541
+ | Doctor | Unified diagnostics: boot/runtime/deep tiers, `--fix` mode | src/doctor/ |
542
+ | Skills v2 | Executable skills with guide/execute/hybrid modes, approval levels | NEXO_HOME/skills/ |
543
+ | Day Orchestrator | Autonomous cycles every 15 min (8:00-23:00) — opt-in | LaunchAgent |
524
544
  | CLAUDE.md | Complete operator instructions (Codex, hooks, guard, trust, memory) | ~/.claude/CLAUDE.md |
525
545
  | Schedule config | schedule.json with customizable process times and timezone | NEXO_HOME/config/ |
526
546
  | Auto-update | Non-blocking startup check (5s max), opt-out via schedule.json | Built into server startup |
@@ -528,6 +548,38 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
528
548
  | Auto-diary | 3-layer system: PostToolUse every 10 calls, PreCompact emergency, heartbeat DIARY_OVERDUE | Built into hooks |
529
549
  | Claude Code config | MCP server + 7 hooks + 15 processes registered | ~/.claude/settings.json |
530
550
 
551
+ ### Runtime CLI
552
+
553
+ 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:
554
+
555
+ ```bash
556
+ # Personal Scripts
557
+ nexo scripts list # List your personal scripts
558
+ nexo scripts run my-script # Run a script with injected NEXO env
559
+ nexo scripts doctor # Validate all personal scripts
560
+ nexo scripts call nexo_learning_search --input '{"query":"cron"}' # Call any MCP tool
561
+
562
+ # Skills v2
563
+ nexo skills sync # Sync filesystem skill definitions into SQLite
564
+ nexo skills list # List published/stable skills
565
+ nexo skills get SK-... # Inspect a skill definition
566
+ nexo skills apply SK-... --dry-run --json # Resolve guide/execute/hybrid without running it
567
+ nexo skills approve SK-... --execution-level local --approved-by Francisco # Optional metadata override
568
+ nexo skills evolution # Show text→script and improvement candidates
569
+
570
+ # Unified Doctor
571
+ nexo doctor # Quick boot diagnostics
572
+ nexo doctor --tier all # Full system check (boot + runtime + deep)
573
+ nexo doctor --tier runtime --json # Machine-readable health report
574
+ nexo doctor --fix # Apply deterministic repairs
575
+ ```
576
+
577
+ Personal scripts live in `NEXO_HOME/scripts/` with inline metadata. See `docs/writing-scripts.md` for details.
578
+
579
+ 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.
580
+
581
+ The Doctor system reads existing health artifacts (immune, watchdog, self-audit) without triggering repairs in default mode.
582
+
531
583
  ### Requirements
532
584
 
533
585
  - **macOS or Linux** (Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))
@@ -741,6 +793,30 @@ If NEXO Brain is useful to you, consider:
741
793
 
742
794
  ## Changelog
743
795
 
796
+ ### v2.5.0 — Runtime CLI, Doctor, Skills v2, Day Orchestrator (2026-04-03)
797
+ - **Runtime CLI** (`nexo`): New operational CLI separate from installer. `nexo scripts list/run/doctor/call` for personal scripts, `nexo doctor` for diagnostics, `nexo skills apply` for executable skills, `nexo update` for one-step sync.
798
+ - **Unified Doctor**: Modular diagnostic system with boot/runtime/deep tiers. Report-only by default, deterministic `--fix` mode. MCP tool `nexo_doctor`. LaunchAgent schedule drift detection and reconciliation.
799
+ - **Skills v2**: Executable skills with guide/execute/hybrid modes. Security levels (read-only/local/remote) with explicit approval. Core vs personal vs community directories. Deep Sleep auto-evolution integration.
800
+ - **Day Orchestrator**: Autonomous NEXO cycles every 15 min (8:00-23:00). Launches Claude Code headless with full MCP. Checks followups, emails, infra — acts autonomously, emails user only when needed. Opt-in.
801
+ - **Dashboard always-on**: Web UI at localhost:6174 as persistent LaunchAgent. 23 modules, Jinja2 templating, dark theme. Opt-in.
802
+ - **Personal Scripts Framework**: Auto-discovery in NEXO_HOME/scripts/, inline metadata, runtime detection, forbidden-pattern validation, vendorable helper, template.
803
+ - Configurable operator name (UserContext singleton), watchdog normalized to 30 min, LaunchAgent drift fix.
804
+
805
+ ### v2.4.0 — Skills, Cron Scheduler, Security, Full Audit (2026-04-03)
806
+ - **Skill Auto-Creation**: Deep Sleep extracts reusable procedures from sessions. Content stored as markdown with steps and gotchas. Trust pipeline with autonomous quality control.
807
+ - **Cron Scheduler**: execution tracking (`cron_runs` table), `nexo_schedule_status` and `nexo_schedule_add` MCP tools, universal cron wrapper for all processes.
808
+ - **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.
809
+ - **Security**: credential redaction in tool logs, transcript sanitization, command injection fix in dashboard, path traversal protection in plugin loader.
810
+ - **Diary filter**: startup only shows human sessions, auto-closed cron sessions filtered out. Email sessions preserved as real interactions.
811
+ - **Preflight CI**: 66 automated checks (py_compile, bash -n, manifest consistency, npm artifact, forbidden markers).
812
+ - **Python 3.9 compat**: `from __future__ import annotations` across 18 files.
813
+ - **Linux**: full systemd timer support, .bashrc alias for interactive shells.
814
+ - Passed 5-phase automated audit: Product, Failure, Security, Packaging, UX.
815
+
816
+ ### v2.2.0 — Trust Score v2 (2026-04-01)
817
+ - **Trust Score**: fair daily calibration from Deep Sleep analysis. Score 0-100 based on corrections, autonomy, proactivity.
818
+ - **Cognitive Quarantine**: new memories go through quarantine before promotion to LTM.
819
+
744
820
  ### v2.0.0 — Unified Architecture (2026-03-31)
745
821
  - **Code/data separation**: Code in repo (`src/`), personal data in `NEXO_HOME` (default `~/.nexo/`). `NEXO_HOME` env var required.
746
822
  - **Plugin loader dual-directory**: Scans `src/plugins/` (base) then `NEXO_HOME/plugins/` (personal override by filename).
package/bin/nexo-brain.js CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  const { execSync, spawnSync } = require("child_process");
18
+ const crypto = require("crypto");
18
19
  const fs = require("fs");
19
20
  const path = require("path");
20
21
  const readline = require("readline");
@@ -31,6 +32,13 @@ const LAUNCH_AGENTS = path.join(
31
32
  "LaunchAgents"
32
33
  );
33
34
 
35
+ function isEphemeralInstall(nexoHome) {
36
+ const homeDir = require("os").homedir();
37
+ const allowEphemeral = process.env.NEXO_ALLOW_EPHEMERAL_INSTALL === "1";
38
+ if (allowEphemeral) return false;
39
+ return nexoHome.startsWith("/tmp/") || homeDir.startsWith("/tmp/");
40
+ }
41
+
34
42
  const rl = readline.createInterface({
35
43
  input: process.stdin,
36
44
  output: process.stdout,
@@ -52,6 +60,33 @@ function log(msg) {
52
60
  console.log(` ${msg}`);
53
61
  }
54
62
 
63
+ function syncWatchdogHashRegistry(nexoHome) {
64
+ try {
65
+ const watchdogPath = path.join(nexoHome, "scripts", "nexo-watchdog.sh");
66
+ if (!fs.existsSync(watchdogPath)) return;
67
+
68
+ const registryPath = path.join(nexoHome, "scripts", ".watchdog-hashes");
69
+ const entries = new Map();
70
+ if (fs.existsSync(registryPath)) {
71
+ for (const line of fs.readFileSync(registryPath, "utf8").split(/\r?\n/)) {
72
+ if (!line.includes("|")) continue;
73
+ const [filePath, expectedHash] = line.split("|");
74
+ if (filePath) entries.set(filePath, expectedHash || "");
75
+ }
76
+ }
77
+
78
+ const digest = crypto.createHash("sha256").update(fs.readFileSync(watchdogPath)).digest("hex");
79
+ entries.set(watchdogPath, digest);
80
+ const body = Array.from(entries.entries())
81
+ .sort(([a], [b]) => a.localeCompare(b))
82
+ .map(([filePath, hash]) => `${filePath}|${hash}`)
83
+ .join("\n");
84
+ fs.writeFileSync(registryPath, `${body}\n`);
85
+ } catch (err) {
86
+ log(`WARN: could not sync watchdog hash registry: ${err.message}`);
87
+ }
88
+ }
89
+
55
90
  // ══════════════════════════════════════════════════════════════════════════════
56
91
  // CORE PROCESS & HOOK DEFINITIONS
57
92
  // All 13 nightly/periodic processes and all 8 core hooks that make NEXO functional.
@@ -66,9 +101,9 @@ const ALL_PROCESSES = [
66
101
  // --- Every 5 minutes ---
67
102
  { name: "auto-close-sessions", script: "auto_close_sessions.py", interpreter: "python", scriptDir: "root",
68
103
  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
104
  // --- Every 30 minutes ---
105
+ { name: "watchdog", script: "nexo-watchdog.sh", interpreter: "bash", scriptDir: "scripts",
106
+ type: "interval", intervalMinutes: 30, purpose: "Health monitoring" },
72
107
  { name: "immune", script: "nexo-immune.py", interpreter: "python", scriptDir: "scripts",
73
108
  type: "interval", intervalMinutes: 30, purpose: "System immunity checks" },
74
109
  // --- Every 2 hours ---
@@ -86,6 +121,10 @@ const ALL_PROCESSES = [
86
121
  // --- KeepAlive (persistent daemon) ---
87
122
  { name: "prevent-sleep", script: "nexo-prevent-sleep.sh", interpreter: "bash", scriptDir: "scripts",
88
123
  type: "keepAlive", purpose: "Keep machine awake for nocturnal processes" },
124
+ { name: "dashboard", script: "nexo-dashboard.sh", interpreter: "bash", scriptDir: "scripts",
125
+ type: "keepAlive", optional: "dashboard", purpose: "Web dashboard at localhost:6174" },
126
+ { name: "day-orchestrator", script: "nexo-day-orchestrator.sh", interpreter: "bash", scriptDir: "scripts",
127
+ type: "keepAlive", optional: "orchestrator", purpose: "Autonomous NEXO cycles every 15 min (8:00-23:00)" },
89
128
  // --- Daily (times from schedule.json) ---
90
129
  { name: "cognitive-decay", script: "nexo-cognitive-decay.py", interpreter: "python", scriptDir: "scripts",
91
130
  type: "daily", defaultHour: 3, defaultMinute: 0, purpose: "Memory decay" },
@@ -262,7 +301,7 @@ const DAY_MAP = {
262
301
  * Linux+systemd: .service + .timer files
263
302
  * Linux fallback: crontab entries
264
303
  */
265
- function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir) {
304
+ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir, enabledOptionals = {}) {
266
305
  const home = require("os").homedir();
267
306
  const nexoCode = nexoHome;
268
307
  const logsDir = path.join(nexoHome, "logs");
@@ -298,6 +337,8 @@ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAge
298
337
  for (const proc of ALL_PROCESSES) {
299
338
  // Skip macOnly processes on Linux
300
339
  if (proc.macOnly && platform !== "darwin") continue;
340
+ // Skip optional processes that weren't enabled
341
+ if (proc.optional && !enabledOptionals[proc.optional]) continue;
301
342
 
302
343
  const plistName = `com.nexo.${proc.name}.plist`;
303
344
  const plistPath = path.join(launchAgentsDir, plistName);
@@ -404,6 +445,7 @@ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAge
404
445
 
405
446
  for (const proc of ALL_PROCESSES) {
406
447
  if (proc.macOnly) continue; // tcc-approve is macOS only
448
+ if (proc.optional && !enabledOptionals[proc.optional]) continue;
407
449
  const serviceName = `nexo-${proc.name}`;
408
450
  const serviceFile = path.join(systemdDir, `${serviceName}.service`);
409
451
  const timerFile = path.join(systemdDir, `${serviceName}.timer`);
@@ -481,6 +523,7 @@ WantedBy=timers.target
481
523
  const envLine2 = `NEXO_CODE=${nexoCode}`;
482
524
 
483
525
  for (const proc of ALL_PROCESSES) {
526
+ if (proc.optional && !enabledOptionals[proc.optional]) continue;
484
527
  const sPath = scriptPath(proc);
485
528
  const interp = interpreterPath(proc);
486
529
  const s = getSchedule(proc);
@@ -729,8 +772,13 @@ async function main() {
729
772
  // Regenerate ALL 13 LaunchAgents / systemd timers
730
773
  const migSchedule = loadOrCreateSchedule(NEXO_HOME);
731
774
  const migPython = findVenvPython(NEXO_HOME) || "python3";
732
- installAllProcesses(platform, migPython, NEXO_HOME, migSchedule, LAUNCH_AGENTS);
733
- log(" All 13 automated processes updated.");
775
+ let migOptionals = {};
776
+ try {
777
+ const optFile = path.join(NEXO_HOME, "config", "optionals.json");
778
+ if (fs.existsSync(optFile)) migOptionals = JSON.parse(fs.readFileSync(optFile, "utf8"));
779
+ } catch {}
780
+ installAllProcesses(platform, migPython, NEXO_HOME, migSchedule, LAUNCH_AGENTS, migOptionals);
781
+ log(" All automated processes updated.");
734
782
 
735
783
  // Update version file
736
784
  fs.writeFileSync(versionFile, JSON.stringify({
@@ -776,7 +824,7 @@ async function main() {
776
824
  // Same version — backfill crons/ if missing (for installs before crons was shipped)
777
825
  const cronsDest = path.join(NEXO_HOME, "crons");
778
826
  const cronsSrc = path.join(__dirname, "..", "src", "crons");
779
- if (!fs.existsSync(path.join(cronsDest, "manifest.json")) && fs.existsSync(cronsSrc)) {
827
+ if (fs.existsSync(cronsSrc)) {
780
828
  const copyDirRec2 = (src, dest) => {
781
829
  fs.mkdirSync(dest, { recursive: true });
782
830
  fs.readdirSync(src).forEach(item => {
@@ -788,7 +836,61 @@ async function main() {
788
836
  });
789
837
  };
790
838
  copyDirRec2(cronsSrc, cronsDest);
791
- log("Backfilled crons/ directory (catchup & watchdog need it).");
839
+ log("Refreshed crons/ directory.");
840
+
841
+ const cronSyncPath = path.join(cronsSrc, "sync.py");
842
+ const syncPython = findVenvPython(NEXO_HOME) || run("which python3") || "python3";
843
+ if (fs.existsSync(cronSyncPath)) {
844
+ const syncResult = spawnSync(syncPython, [cronSyncPath], {
845
+ env: { ...process.env, NEXO_HOME, NEXO_CODE: path.join(__dirname, "..", "src") },
846
+ stdio: "pipe",
847
+ encoding: "utf8",
848
+ });
849
+ if (syncResult.status === 0) {
850
+ log("Core crons reconciled with manifest.");
851
+ } else {
852
+ const syncErr = (syncResult.stderr || syncResult.stdout || "").trim();
853
+ log(`Cron sync warning: ${syncErr || `exit ${syncResult.status}`}`);
854
+ }
855
+ }
856
+ }
857
+
858
+ // Same version — refresh packaged core skills/templates/runtime helpers too.
859
+ const skillsCoreDest = path.join(NEXO_HOME, "skills-core");
860
+ const skillsCoreSrc = path.join(__dirname, "..", "src", "skills");
861
+ if (fs.existsSync(skillsCoreSrc)) {
862
+ const copyDirRec3 = (src, dest) => {
863
+ fs.mkdirSync(dest, { recursive: true });
864
+ fs.readdirSync(src).forEach(item => {
865
+ if (item === "__pycache__" || item.endsWith(".pyc")) return;
866
+ const srcP = path.join(src, item);
867
+ const destP = path.join(dest, item);
868
+ if (fs.statSync(srcP).isDirectory()) copyDirRec3(srcP, destP);
869
+ else fs.copyFileSync(srcP, destP);
870
+ });
871
+ };
872
+ copyDirRec3(skillsCoreSrc, skillsCoreDest);
873
+ log("Refreshed skills-core/ directory.");
874
+ }
875
+
876
+ ["skills_runtime.py"].forEach((fname) => {
877
+ const srcFile = path.join(__dirname, "..", "src", fname);
878
+ const destFile = path.join(NEXO_HOME, fname);
879
+ if (fs.existsSync(srcFile)) {
880
+ fs.copyFileSync(srcFile, destFile);
881
+ }
882
+ });
883
+
884
+ const templatesSrc = path.join(__dirname, "..", "templates");
885
+ const templatesDest = path.join(NEXO_HOME, "templates");
886
+ if (fs.existsSync(templatesSrc)) {
887
+ fs.mkdirSync(templatesDest, { recursive: true });
888
+ ["script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"].forEach((f) => {
889
+ const src = path.join(templatesSrc, f);
890
+ if (fs.existsSync(src)) {
891
+ fs.copyFileSync(src, path.join(templatesDest, f));
892
+ }
893
+ });
792
894
  }
793
895
 
794
896
  log(`Already at v${currentVersion}. No migration needed.`);
@@ -895,6 +997,12 @@ async function main() {
895
997
  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
998
  caffYes: "Nocturnal processes scheduled.",
897
999
  caffNo: "Ok, I'll run them when I can.",
1000
+ 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 > ",
1001
+ dashYes: "Dashboard enabled.",
1002
+ dashNo: "Dashboard disabled. You can start it manually: nexo dashboard",
1003
+ 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 > ",
1004
+ orchYes: "Autonomous mode enabled. I'll be working for you 8:00-23:00.",
1005
+ orchNo: "Autonomous mode disabled. I'll only work when you open a session.",
898
1006
  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
1007
  autoInstallYes: "Auto-install enabled.",
900
1008
  autoInstallNo: "I'll ask before installing.",
@@ -924,6 +1032,12 @@ async function main() {
924
1032
  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
1033
  caffYes: "Procesos nocturnos programados.",
926
1034
  caffNo: "Ok, los ejecutaré cuando pueda.",
1035
+ 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 > ",
1036
+ dashYes: "Dashboard activado.",
1037
+ dashNo: "Dashboard desactivado. Puedes iniciarlo manualmente: nexo dashboard",
1038
+ 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 > ",
1039
+ orchYes: "Modo autónomo activado. Estaré trabajando para ti de 8:00 a 23:00.",
1040
+ orchNo: "Modo autónomo desactivado. Solo trabajo cuando abras sesión.",
927
1041
  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
1042
  autoInstallYes: "Auto-instalación activada.",
929
1043
  autoInstallNo: "Te preguntaré antes.",
@@ -953,6 +1067,12 @@ async function main() {
953
1067
  caffeinateQ: " Garder le Mac éveillé pour mes processus nocturnes ?\n 1. Oui\n 2. Non\n > ",
954
1068
  caffYes: "Processus nocturnes programmés.",
955
1069
  caffNo: "Ok, je les exécuterai quand possible.",
1070
+ 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 > ",
1071
+ dashYes: "Dashboard activé.",
1072
+ dashNo: "Dashboard désactivé. Démarrage manuel : nexo dashboard",
1073
+ 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 > ",
1074
+ orchYes: "Mode autonome activé. Je travaille pour vous de 8h à 23h.",
1075
+ orchNo: "Mode autonome désactivé. Je travaille uniquement en session.",
956
1076
  autoInstallQ: " Puis-je installer des outils automatiquement ? (brew, pip, npm)\n 1. Oui\n 2. Demande-moi avant\n > ",
957
1077
  autoInstallYes: "Auto-installation activée.",
958
1078
  autoInstallNo: "Je demanderai avant.",
@@ -982,6 +1102,12 @@ async function main() {
982
1102
  caffeinateQ: " Mac wach halten für nächtliche Prozesse?\n 1. Ja\n 2. Nein\n > ",
983
1103
  caffYes: "Nachtprozesse geplant.",
984
1104
  caffNo: "Ok, führe sie aus wenn möglich.",
1105
+ dashboardQ: " Web-Dashboard auf localhost:6174 aktivieren?\n (Immer aktive UI für Speicher, Sitzungen und Systemgesundheit)\n 1. Ja\n 2. Nein\n > ",
1106
+ dashYes: "Dashboard aktiviert.",
1107
+ dashNo: "Dashboard deaktiviert. Manuell starten: nexo dashboard",
1108
+ 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 > ",
1109
+ orchYes: "Autonomer Modus aktiviert. Ich arbeite für dich von 8:00 bis 23:00.",
1110
+ orchNo: "Autonomer Modus deaktiviert. Ich arbeite nur in Sitzungen.",
985
1111
  autoInstallQ: " Darf ich Tools automatisch installieren? (brew, pip, npm)\n 1. Ja\n 2. Frag mich vorher\n > ",
986
1112
  autoInstallYes: "Auto-Installation aktiviert.",
987
1113
  autoInstallNo: "Frage vorher.",
@@ -1011,6 +1137,12 @@ async function main() {
1011
1137
  caffeinateQ: " Tenere il Mac sveglio per i processi notturni?\n 1. Sì\n 2. No\n > ",
1012
1138
  caffYes: "Processi notturni programmati.",
1013
1139
  caffNo: "Ok, li eseguirò quando possibile.",
1140
+ 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 > ",
1141
+ dashYes: "Dashboard attivata.",
1142
+ dashNo: "Dashboard disattivata. Avvio manuale: nexo dashboard",
1143
+ orchestratorQ: " Attivare la modalità autonoma? (Lavoro da solo ogni 15 min: followup, email, infra, report via email)\n 1. Sì\n 2. No\n > ",
1144
+ orchYes: "Modalità autonoma attivata. Lavoro per te dalle 8:00 alle 23:00.",
1145
+ orchNo: "Modalità autonoma disattivata. Lavoro solo nelle sessioni.",
1014
1146
  autoInstallQ: " Posso installare strumenti automaticamente? (brew, pip, npm)\n 1. Sì\n 2. Chiedimi prima\n > ",
1015
1147
  autoInstallYes: "Auto-installazione attivata.",
1016
1148
  autoInstallNo: "Chiederò prima.",
@@ -1040,6 +1172,12 @@ async function main() {
1040
1172
  caffeinateQ: " Manter o Mac acordado para processos noturnos?\n 1. Sim\n 2. Não\n > ",
1041
1173
  caffYes: "Processos noturnos agendados.",
1042
1174
  caffNo: "Ok, executo quando possível.",
1175
+ 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 > ",
1176
+ dashYes: "Dashboard ativado.",
1177
+ dashNo: "Dashboard desativado. Iniciar manualmente: nexo dashboard",
1178
+ 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 > ",
1179
+ orchYes: "Modo autônomo ativado. Trabalho para você das 8:00 às 23:00.",
1180
+ orchNo: "Modo autônomo desativado. Trabalho apenas nas sessões.",
1043
1181
  autoInstallQ: " Posso instalar ferramentas automaticamente? (brew, pip, npm)\n 1. Sim\n 2. Pergunta antes\n > ",
1044
1182
  autoInstallYes: "Auto-instalação ativada.",
1045
1183
  autoInstallNo: "Perguntarei antes.",
@@ -1158,6 +1296,8 @@ async function main() {
1158
1296
  // Step 5: Deep scan (P9)
1159
1297
  let doScan = false;
1160
1298
  let doCaffeinate = false;
1299
+ let doDashboard = false;
1300
+ let doOrchestrator = false;
1161
1301
  let autoInstall = "ask";
1162
1302
  if (!useDefaults) {
1163
1303
  const scanAnswer = await ask(t.scanQ);
@@ -1172,6 +1312,18 @@ async function main() {
1172
1312
  console.log("");
1173
1313
  }
1174
1314
 
1315
+ // Step 6b: Dashboard — always-on web UI
1316
+ const dashAnswer = await ask(t.dashboardQ);
1317
+ doDashboard = dashAnswer.trim() === "1" || dashAnswer.trim().toLowerCase().startsWith("y") || dashAnswer.trim().toLowerCase().startsWith("s");
1318
+ log(doDashboard ? `✓ ${t.dashYes}` : t.dashNo);
1319
+ console.log("");
1320
+
1321
+ // Step 6c: Day Orchestrator — autonomous NEXO cycles
1322
+ const orchAnswer = await ask(t.orchestratorQ);
1323
+ doOrchestrator = orchAnswer.trim() === "1" || orchAnswer.trim().toLowerCase().startsWith("y") || orchAnswer.trim().toLowerCase().startsWith("s");
1324
+ log(doOrchestrator ? `✓ ${t.orchYes}` : t.orchNo);
1325
+ console.log("");
1326
+
1175
1327
  // Step 7: Auto-install permission (P11)
1176
1328
  const autoInstallAnswer = await ask(t.autoInstallQ);
1177
1329
  autoInstall = (autoInstallAnswer.trim() === "1" || autoInstallAnswer.trim().toLowerCase().startsWith("y") || autoInstallAnswer.trim().toLowerCase().startsWith("s")) ? "auto" : "ask";
@@ -1224,8 +1376,12 @@ async function main() {
1224
1376
  log("Setting up NEXO home...");
1225
1377
  const dirs = [
1226
1378
  NEXO_HOME,
1379
+ path.join(NEXO_HOME, "bin"),
1227
1380
  path.join(NEXO_HOME, "plugins"),
1228
1381
  path.join(NEXO_HOME, "scripts"),
1382
+ path.join(NEXO_HOME, "skills"),
1383
+ path.join(NEXO_HOME, "skills-core"),
1384
+ path.join(NEXO_HOME, "skills-runtime"),
1229
1385
  path.join(NEXO_HOME, "logs"),
1230
1386
  path.join(NEXO_HOME, "backups"),
1231
1387
  path.join(NEXO_HOME, "coordination"),
@@ -1242,7 +1398,7 @@ async function main() {
1242
1398
  objective: "Improve operational excellence and reduce repeated errors",
1243
1399
  focus_areas: ["error_prevention", "proactivity", "memory_quality"],
1244
1400
  evolution_enabled: true,
1245
- evolution_mode: "review",
1401
+ evolution_mode: "auto",
1246
1402
  dimensions: {
1247
1403
  episodic_memory: { current: 0, target: 90 },
1248
1404
  autonomy: { current: 0, target: 80 },
@@ -1275,6 +1431,7 @@ async function main() {
1275
1431
  const srcDir = path.join(__dirname, "..", "src");
1276
1432
  const pluginsSrcDir = path.join(srcDir, "plugins");
1277
1433
  const scriptsSrcDir = path.join(srcDir, "scripts");
1434
+ const skillsSrcDir = path.join(srcDir, "skills");
1278
1435
  const templateDir = path.join(__dirname, "..", "templates");
1279
1436
 
1280
1437
  // Recursive copy helper (skips __pycache__, .pyc, .db files)
@@ -1315,6 +1472,9 @@ async function main() {
1315
1472
  "tools_task_history.py",
1316
1473
  "tools_menu.py",
1317
1474
  "requirements.txt",
1475
+ "cli.py",
1476
+ "script_registry.py",
1477
+ "skills_runtime.py",
1318
1478
  ];
1319
1479
  coreFiles.forEach((f) => {
1320
1480
  const src = path.join(srcDir, f);
@@ -1323,8 +1483,31 @@ async function main() {
1323
1483
  }
1324
1484
  });
1325
1485
 
1486
+ // Runtime CLI wrapper lives in NEXO_HOME/bin so it survives npx installs.
1487
+ const runtimeCli = [
1488
+ "#!/usr/bin/env bash",
1489
+ "set -euo pipefail",
1490
+ "",
1491
+ `NEXO_HOME="${NEXO_HOME}"`,
1492
+ 'PYTHON="$NEXO_HOME/.venv/bin/python3"',
1493
+ 'if [ ! -x "$PYTHON" ]; then',
1494
+ ' if command -v python3 >/dev/null 2>&1; then',
1495
+ ' PYTHON="python3"',
1496
+ " else",
1497
+ ' PYTHON="python"',
1498
+ " fi",
1499
+ "fi",
1500
+ 'export NEXO_HOME',
1501
+ 'export NEXO_CODE="$NEXO_HOME"',
1502
+ 'exec "$PYTHON" "$NEXO_HOME/cli.py" "$@"',
1503
+ "",
1504
+ ].join("\n");
1505
+ const runtimeCliPath = path.join(NEXO_HOME, "bin", "nexo");
1506
+ fs.writeFileSync(runtimeCliPath, runtimeCli);
1507
+ fs.chmodSync(runtimeCliPath, 0o755);
1508
+
1326
1509
  // Core packages (directories with __init__.py)
1327
- ["db", "cognitive"].forEach(pkg => {
1510
+ ["db", "cognitive", "doctor"].forEach(pkg => {
1328
1511
  const pkgSrc = path.join(srcDir, pkg);
1329
1512
  if (fs.existsSync(pkgSrc)) {
1330
1513
  copyDirRecursive(pkgSrc, path.join(NEXO_HOME, pkg));
@@ -1347,6 +1530,13 @@ async function main() {
1347
1530
  fs.readdirSync(scriptsDest).filter(f => f.endsWith(".sh")).forEach(f => {
1348
1531
  fs.chmodSync(path.join(scriptsDest, f), "755");
1349
1532
  });
1533
+ syncWatchdogHashRegistry(NEXO_HOME);
1534
+ }
1535
+
1536
+ // Core skills are shipped separately from personal skills.
1537
+ if (fs.existsSync(skillsSrcDir)) {
1538
+ copyDirRecursive(skillsSrcDir, path.join(NEXO_HOME, "skills-core"));
1539
+ log(" Core skills installed.");
1350
1540
  }
1351
1541
 
1352
1542
  // Dashboard (recursive — includes static/, templates/)
@@ -1370,6 +1560,19 @@ async function main() {
1370
1560
  log(" Crons installed.");
1371
1561
  }
1372
1562
 
1563
+ // Templates directory (scripts + skills scaffolds)
1564
+ const templatesDest = path.join(NEXO_HOME, "templates");
1565
+ fs.mkdirSync(templatesDest, { recursive: true });
1566
+ if (fs.existsSync(templateDir)) {
1567
+ ["script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"].forEach(f => {
1568
+ const src = path.join(templateDir, f);
1569
+ if (fs.existsSync(src)) {
1570
+ fs.copyFileSync(src, path.join(templatesDest, f));
1571
+ }
1572
+ });
1573
+ log(" Script and skill templates installed.");
1574
+ }
1575
+
1373
1576
  // Hooks directory
1374
1577
  const hooksSrcDir = path.join(srcDir, "hooks");
1375
1578
  if (fs.existsSync(hooksSrcDir)) {
@@ -1862,12 +2065,25 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1862
2065
  // Step 7: Create schedule.json (only on fresh install) and install ALL 13 processes
1863
2066
  log("Setting up automated processes...");
1864
2067
  const schedule = loadOrCreateSchedule(NEXO_HOME);
1865
- installAllProcesses(platform, python, NEXO_HOME, schedule, LAUNCH_AGENTS);
2068
+ const enabledOptionals = { dashboard: doDashboard, orchestrator: doOrchestrator };
2069
+ if (isEphemeralInstall(NEXO_HOME)) {
2070
+ log("Ephemeral HOME/NEXO_HOME detected — skipping LaunchAgents installation.");
2071
+ } else {
2072
+ installAllProcesses(platform, python, NEXO_HOME, schedule, LAUNCH_AGENTS, enabledOptionals);
2073
+ }
2074
+
2075
+ // Persist optional process preferences for auto-update
2076
+ try {
2077
+ const configDir = path.join(NEXO_HOME, "config");
2078
+ fs.mkdirSync(configDir, { recursive: true });
2079
+ const optFile = path.join(configDir, "optionals.json");
2080
+ fs.writeFileSync(optFile, JSON.stringify(enabledOptionals, null, 2));
2081
+ } catch {}
1866
2082
 
1867
2083
  // Note: prevent-sleep and tcc-approve are now part of ALL_PROCESSES
1868
2084
  // and installed by installAllProcesses() above. No separate caffeinate block needed.
1869
2085
 
1870
- // Step 8: Create shell alias so user can just type the operator's name
2086
+ // Step 8: Create shell alias and add runtime CLI to PATH
1871
2087
  log("Creating shell alias...");
1872
2088
  const aliasName = operatorName.toLowerCase();
1873
2089
  const savedCliPath = (() => {
@@ -1877,6 +2093,8 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1877
2093
  const claudeBin = savedCliPath || run("which claude") || "claude";
1878
2094
  const aliasLine = `alias ${aliasName}='${claudeBin} --dangerously-skip-permissions "."'`;
1879
2095
  const aliasComment = `# ${operatorName} — start Claude Code with ${operatorName} speaking first`;
2096
+ const nexoPathLine = `export PATH="${path.join(NEXO_HOME, "bin")}:$PATH"`;
2097
+ const nexoPathComment = "# NEXO runtime CLI";
1880
2098
 
1881
2099
  // Detect shell and add alias
1882
2100
  const userShell = process.env.SHELL || "/bin/bash";
@@ -1899,6 +2117,14 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1899
2117
  rcContent = fs.readFileSync(rcFile, "utf8");
1900
2118
  }
1901
2119
 
2120
+ if (!rcContent.includes(nexoPathLine)) {
2121
+ fs.appendFileSync(rcFile, `\n${nexoPathComment}\n${nexoPathLine}\n`);
2122
+ log(`Added NEXO runtime CLI to ${path.basename(rcFile)}`);
2123
+ rcContent += `\n${nexoPathComment}\n${nexoPathLine}\n`;
2124
+ } else {
2125
+ log(`Runtime CLI already present in ${path.basename(rcFile)}`);
2126
+ }
2127
+
1902
2128
  if (!rcContent.includes(`alias ${aliasName}=`)) {
1903
2129
  fs.appendFileSync(rcFile, `\n${aliasComment}\n${aliasLine}\n`);
1904
2130
  log(`Added '${aliasName}' alias to ${path.basename(rcFile)}`);
@@ -1906,7 +2132,7 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
1906
2132
  log(`Alias '${aliasName}' already exists in ${path.basename(rcFile)}`);
1907
2133
  }
1908
2134
  }
1909
- log(`After setup, open a new terminal and type: ${aliasName}`);
2135
+ log(`After setup, open a new terminal and type: ${aliasName} or nexo`);
1910
2136
  console.log("");
1911
2137
 
1912
2138
  // 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
+