nexo-brain 7.1.1 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +3 -2
  3. package/bin/nexo-brain.js +198 -92
  4. package/package.json +1 -1
  5. package/src/agent_runner.py +10 -8
  6. package/src/auto_close_sessions.py +19 -2
  7. package/src/auto_update.py +232 -28
  8. package/src/autonomy_mandate.py +260 -0
  9. package/src/bootstrap_docs.py +22 -1
  10. package/src/cli.py +181 -1
  11. package/src/cli_email.py +104 -73
  12. package/src/client_sync.py +22 -1
  13. package/src/cognitive/_core.py +5 -3
  14. package/src/core_prompts.py +50 -0
  15. package/src/cron_recovery.py +81 -7
  16. package/src/crons/manifest.json +57 -0
  17. package/src/crons/sync.py +95 -26
  18. package/src/dashboard/app.py +59 -0
  19. package/src/dashboard/templates/base.html +2 -0
  20. package/src/dashboard/templates/feature-disabled.html +27 -0
  21. package/src/db/_email_accounts.py +67 -18
  22. package/src/db/_fts.py +5 -5
  23. package/src/db/_personal_scripts.py +1 -1
  24. package/src/db/_skills.py +3 -3
  25. package/src/doctor/providers/runtime.py +35 -20
  26. package/src/email_config.py +18 -9
  27. package/src/enforcement_classifier.py +3 -12
  28. package/src/evolution_cycle.py +37 -149
  29. package/src/guardian_telemetry.py +3 -2
  30. package/src/hook_guardrails.py +61 -0
  31. package/src/hooks/capture-tool-logs.sh +11 -3
  32. package/src/hooks/daily-briefing-check.sh +7 -2
  33. package/src/hooks/heartbeat-enforcement.py +14 -1
  34. package/src/hooks/heartbeat-posttool.sh +2 -0
  35. package/src/hooks/heartbeat-user-msg.sh +2 -0
  36. package/src/hooks/inbox-hook.sh +6 -2
  37. package/src/hooks/post-compact.sh +12 -4
  38. package/src/hooks/pre-compact.sh +12 -4
  39. package/src/migrate_embeddings.py +5 -3
  40. package/src/nexo_migrate.py +3 -1
  41. package/src/plugin_loader.py +14 -5
  42. package/src/plugins/adaptive_mode.py +4 -1
  43. package/src/plugins/backup.py +32 -20
  44. package/src/plugins/evolution.py +2 -0
  45. package/src/plugins/memory_export.py +6 -1
  46. package/src/plugins/personal_plugins.py +17 -7
  47. package/src/plugins/personal_scripts.py +64 -3
  48. package/src/presets/entities_universal.json +67 -4
  49. package/src/product_mode.py +201 -0
  50. package/src/r14_correction_learning.py +5 -20
  51. package/src/r15_project_context.py +4 -10
  52. package/src/r16_declared_done.py +3 -16
  53. package/src/r17_promise_debt.py +3 -16
  54. package/src/r18_followup_autocomplete.py +5 -7
  55. package/src/r19_project_grep.py +5 -8
  56. package/src/r20_constant_change.py +5 -15
  57. package/src/r21_legacy_path.py +5 -7
  58. package/src/r22_personal_script.py +4 -8
  59. package/src/r23_ssh_without_atlas.py +4 -11
  60. package/src/r23b_deploy_vhost.py +7 -6
  61. package/src/r23c_cwd_mismatch.py +7 -6
  62. package/src/r23d_chown_chmod_recursive.py +6 -6
  63. package/src/r23e_force_push_main.py +5 -6
  64. package/src/r23f_db_no_where.py +5 -6
  65. package/src/r23g_secrets_in_output.py +5 -5
  66. package/src/r23h_shebang_mismatch.py +6 -5
  67. package/src/r23i_auto_deploy_ignored.py +5 -6
  68. package/src/r23j_global_install.py +5 -6
  69. package/src/r23k_script_duplicates_skill.py +7 -6
  70. package/src/r23l_resource_collision.py +7 -6
  71. package/src/r23m_message_duplicate.py +6 -5
  72. package/src/r24_stale_memory.py +4 -9
  73. package/src/r25_nora_maria_read_only.py +5 -10
  74. package/src/r34_identity_coherence.py +6 -13
  75. package/src/r_catalog.py +3 -7
  76. package/src/resonance_map.py +13 -13
  77. package/src/runtime_power.py +29 -80
  78. package/src/script_registry.py +236 -6
  79. package/src/scripts/check-context.py +8 -25
  80. package/src/scripts/deep-sleep/extract.py +6 -10
  81. package/src/scripts/nexo-auto-update.py +27 -4
  82. package/src/scripts/nexo-catchup.py +9 -19
  83. package/src/scripts/nexo-cognitive-decay.py +26 -3
  84. package/src/scripts/nexo-daily-self-audit.py +50 -51
  85. package/src/scripts/nexo-email-migrate-config.py +30 -11
  86. package/src/scripts/nexo-email-monitor.py +97 -238
  87. package/src/scripts/nexo-followup-runner.py +70 -133
  88. package/src/scripts/nexo-hook-record.py +1 -1
  89. package/src/scripts/nexo-immune.py +6 -31
  90. package/src/scripts/nexo-impact-scorer.py +27 -4
  91. package/src/scripts/nexo-learning-housekeep.py +26 -3
  92. package/src/scripts/nexo-learning-validator.py +34 -32
  93. package/src/scripts/nexo-migrate.py +28 -12
  94. package/src/scripts/nexo-morning-agent.py +9 -23
  95. package/src/scripts/nexo-outcome-checker.py +27 -4
  96. package/src/scripts/nexo-postmortem-consolidator.py +30 -62
  97. package/src/scripts/nexo-pre-commit.py +28 -0
  98. package/src/scripts/nexo-proactive-dashboard.py +27 -0
  99. package/src/scripts/nexo-reflection.py +33 -3
  100. package/src/scripts/nexo-runtime-preflight.py +27 -2
  101. package/src/scripts/nexo-send-reply.py +10 -8
  102. package/src/scripts/nexo-sleep.py +11 -25
  103. package/src/scripts/nexo-synthesis.py +7 -40
  104. package/src/scripts/nexo-watchdog-smoke.py +30 -1
  105. package/src/scripts/nexo-watchdog.sh +23 -17
  106. package/src/scripts/phase_guardian_analysis.py +27 -4
  107. package/src/server.py +14 -3
  108. package/src/storage_router.py +8 -6
  109. package/src/tools_drive.py +5 -13
  110. package/src/tools_guardian.py +3 -4
  111. package/src/tools_menu.py +2 -2
  112. package/src/tools_reminders_crud.py +17 -0
  113. package/src/tools_sessions.py +1 -4
  114. package/src/user_context.py +3 -6
  115. package/src/user_data_portability.py +31 -23
  116. package/templates/CLAUDE.md.template +11 -3
  117. package/templates/CODEX.AGENTS.md.template +11 -3
  118. package/templates/core-prompts/catchup-assessment.md +19 -0
  119. package/templates/core-prompts/check-context.md +24 -0
  120. package/templates/core-prompts/daily-self-audit.md +42 -0
  121. package/templates/core-prompts/daily-synthesis.md +40 -0
  122. package/templates/core-prompts/deep-sleep-extract-json-output.md +8 -0
  123. package/templates/core-prompts/drive-signal-classifier-system.md +4 -0
  124. package/templates/core-prompts/drive-signal-classifier-user.md +6 -0
  125. package/templates/core-prompts/email-monitor.md +202 -0
  126. package/templates/core-prompts/enforcement-classifier-retry.md +1 -0
  127. package/templates/core-prompts/enforcement-classifier-strict.md +1 -0
  128. package/templates/core-prompts/evolution-public-contribution.md +32 -0
  129. package/templates/core-prompts/evolution-public-pr-review.md +38 -0
  130. package/templates/core-prompts/evolution-weekly.md +71 -0
  131. package/templates/core-prompts/followup-runner-operator-attention-context.md +4 -0
  132. package/templates/core-prompts/followup-runner-operator-attention-question.md +1 -0
  133. package/templates/core-prompts/followup-runner.md +74 -0
  134. package/templates/core-prompts/immune-triage.md +31 -0
  135. package/templates/core-prompts/interactive-startup.md +1 -0
  136. package/templates/core-prompts/json-object-only.md +1 -0
  137. package/templates/core-prompts/learning-validator.md +25 -0
  138. package/templates/core-prompts/morning-agent-json-output.md +1 -0
  139. package/templates/core-prompts/morning-agent.md +23 -0
  140. package/templates/core-prompts/postmortem-consolidator.md +60 -0
  141. package/templates/core-prompts/r-catalog.md +1 -0
  142. package/templates/core-prompts/r14-correction-learning-injection.md +1 -0
  143. package/templates/core-prompts/r14-correction-learning-question.md +1 -0
  144. package/templates/core-prompts/r15-project-context-injection.md +1 -0
  145. package/templates/core-prompts/r16-declared-done-injection.md +1 -0
  146. package/templates/core-prompts/r16-declared-done-question.md +1 -0
  147. package/templates/core-prompts/r17-promise-debt-injection.md +1 -0
  148. package/templates/core-prompts/r17-promise-debt-question.md +1 -0
  149. package/templates/core-prompts/r18-followup-autocomplete-injection.md +3 -0
  150. package/templates/core-prompts/r19-project-grep-injection.md +1 -0
  151. package/templates/core-prompts/r20-constant-change-injection.md +1 -0
  152. package/templates/core-prompts/r20-constant-change-question.md +1 -0
  153. package/templates/core-prompts/r21-legacy-path-injection.md +1 -0
  154. package/templates/core-prompts/r22-personal-script-injection.md +1 -0
  155. package/templates/core-prompts/r23-ssh-without-atlas-injection.md +1 -0
  156. package/templates/core-prompts/r23b-deploy-vhost-injection.md +1 -0
  157. package/templates/core-prompts/r23c-cwd-mismatch-injection.md +1 -0
  158. package/templates/core-prompts/r23d-chown-chmod-recursive-injection.md +1 -0
  159. package/templates/core-prompts/r23e-force-push-main-injection.md +1 -0
  160. package/templates/core-prompts/r23f-db-no-where-injection.md +1 -0
  161. package/templates/core-prompts/r23g-secrets-in-output-injection.md +1 -0
  162. package/templates/core-prompts/r23h-shebang-mismatch-injection.md +1 -0
  163. package/templates/core-prompts/r23i-auto-deploy-ignored-injection.md +1 -0
  164. package/templates/core-prompts/r23j-global-install-injection.md +1 -0
  165. package/templates/core-prompts/r23k-script-duplicates-skill-injection.md +1 -0
  166. package/templates/core-prompts/r23l-resource-collision-injection.md +1 -0
  167. package/templates/core-prompts/r23m-message-duplicate-injection.md +1 -0
  168. package/templates/core-prompts/r24-stale-memory-injection.md +1 -0
  169. package/templates/core-prompts/r25-read-only-host-injection.md +1 -0
  170. package/templates/core-prompts/r34-identity-coherence-probe.md +1 -0
  171. package/templates/core-prompts/r34-identity-coherence-question.md +1 -0
  172. package/templates/core-prompts/sleep.md +25 -0
  173. package/templates/email-template.md +55 -0
  174. package/templates/nexo_helper.py +31 -13
  175. package/templates/plugin-template.py +3 -3
  176. package/templates/skill-template.md +2 -1
package/bin/nexo-brain.js CHANGED
@@ -318,6 +318,50 @@ function syncRuntimePackageMetadata(repoRoot = path.join(__dirname, ".."), runti
318
318
  }
319
319
  }
320
320
 
321
+ function resolveRuntimeConfigDir(nexoHome) {
322
+ const canonical = path.join(nexoHome, "personal", "config");
323
+ const legacy = path.join(nexoHome, "config");
324
+ if (fs.existsSync(canonical)) return canonical;
325
+ if (fs.existsSync(legacy)) return legacy;
326
+ return canonical;
327
+ }
328
+
329
+ function resolveRuntimeBrainDir(nexoHome) {
330
+ const canonical = path.join(nexoHome, "personal", "brain");
331
+ const legacy = path.join(nexoHome, "brain");
332
+ if (fs.existsSync(canonical)) return canonical;
333
+ if (fs.existsSync(legacy)) return legacy;
334
+ return canonical;
335
+ }
336
+
337
+ function resolveRuntimeDataDir(nexoHome) {
338
+ const canonical = path.join(nexoHome, "runtime", "data");
339
+ const legacy = path.join(nexoHome, "data");
340
+ if (fs.existsSync(canonical)) return canonical;
341
+ if (fs.existsSync(legacy)) return legacy;
342
+ return canonical;
343
+ }
344
+
345
+ function resolveRuntimeLogsDir(nexoHome) {
346
+ const canonical = path.join(nexoHome, "runtime", "logs");
347
+ const legacy = path.join(nexoHome, "logs");
348
+ if (fs.existsSync(canonical)) return canonical;
349
+ if (fs.existsSync(legacy)) return legacy;
350
+ return canonical;
351
+ }
352
+
353
+ function resolveRuntimeCronsDir(nexoHome) {
354
+ const canonical = path.join(nexoHome, "runtime", "crons");
355
+ const legacy = path.join(nexoHome, "crons");
356
+ if (fs.existsSync(canonical)) return canonical;
357
+ if (fs.existsSync(legacy)) return legacy;
358
+ return canonical;
359
+ }
360
+
361
+ function resolveRuntimeSchedulePath(nexoHome) {
362
+ return path.join(resolveRuntimeConfigDir(nexoHome), "schedule.json");
363
+ }
364
+
321
365
  function finalizeF06Layout(python, nexoHome = NEXO_HOME) {
322
366
  try {
323
367
  const result = spawnSync(
@@ -327,7 +371,6 @@ function finalizeF06Layout(python, nexoHome = NEXO_HOME) {
327
371
  [
328
372
  "import auto_update",
329
373
  "auto_update._maybe_migrate_to_f06_layout()",
330
- "auto_update._ensure_f06_legacy_shims()",
331
374
  "auto_update._rewrite_f06_launch_agents()",
332
375
  ].join("; "),
333
376
  ],
@@ -418,7 +461,7 @@ function getCoreRuntimePackages() {
418
461
  // getCoreRuntimeFlatFiles for that reason) but data contracts with a stable
419
462
  // path that external clients read. Keep in sync with docs/contracts/.
420
463
  function publishBrainContracts(srcDir = path.join(__dirname, "..", "src"), nexoHome = NEXO_HOME) {
421
- const brainDir = path.join(nexoHome, "brain");
464
+ const brainDir = resolveRuntimeBrainDir(nexoHome);
422
465
  fs.mkdirSync(brainDir, { recursive: true });
423
466
  const contracts = ["resonance_tiers.json"];
424
467
  contracts.forEach((name) => {
@@ -458,7 +501,7 @@ function resolveLaunchAgentPath(home) {
458
501
 
459
502
  function setupKeychainPassFile(nexoHome) {
460
503
  if (process.platform !== "darwin") return;
461
- const configDir = path.join(nexoHome, "config");
504
+ const configDir = resolveRuntimeConfigDir(nexoHome);
462
505
  const passFile = path.join(configDir, ".keychain-pass");
463
506
  if (fs.existsSync(passFile)) return; // already set up
464
507
  fs.mkdirSync(configDir, { recursive: true });
@@ -528,7 +571,7 @@ function detectFullDiskAccessReasons(nexoHome) {
528
571
  reasons.push(`NEXO_HOME is inside a protected macOS folder: ${nexoHome}`);
529
572
  }
530
573
 
531
- const logsDir = path.join(nexoHome, "logs");
574
+ const logsDir = resolveRuntimeLogsDir(nexoHome);
532
575
  if (fs.existsSync(logsDir)) {
533
576
  const candidates = fs.readdirSync(logsDir).filter((name) => name.endsWith("-stderr.log"));
534
577
  for (const name of candidates) {
@@ -586,25 +629,25 @@ async function maybeConfigureFullDiskAccess(schedule, useDefaults, pythonPath =
586
629
  schedule.full_disk_access_reasons = reasons;
587
630
 
588
631
  if (process.platform !== "darwin" || !reasons.length) {
589
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
632
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
590
633
  return schedule;
591
634
  }
592
635
 
593
636
  if (current === "granted") {
594
637
  const probe = probeFullDiskAccess(NEXO_HOME);
595
638
  if (probe.granted) {
596
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
639
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
597
640
  return schedule;
598
641
  }
599
642
  schedule.full_disk_access_status = "later";
600
643
  } else if (current === "declined") {
601
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
644
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
602
645
  return schedule;
603
646
  }
604
647
 
605
648
  if (useDefaults || !process.stdin.isTTY || !process.stdout.isTTY) {
606
649
  schedule.full_disk_access_status = current === "granted" ? "later" : current || "unset";
607
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
650
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
608
651
  return schedule;
609
652
  }
610
653
 
@@ -640,7 +683,7 @@ async function maybeConfigureFullDiskAccess(schedule, useDefaults, pythonPath =
640
683
  schedule.full_disk_access_status = "declined";
641
684
  }
642
685
 
643
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
686
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
644
687
  return schedule;
645
688
  }
646
689
 
@@ -737,7 +780,7 @@ function _manifestHookEntries() {
737
780
 
738
781
  function _hookCommand(hook, hooksDir, nexoHome) {
739
782
  // Resolve handler path under the installed runtime. hooksDir points to
740
- // ~/.nexo/hooks, which is the copy of src/hooks/ at install time.
783
+ // the canonical installed hook tree (preferably ~/.nexo/core/hooks).
741
784
  const handlerFile = path.basename(hook.handler);
742
785
  const runtimePath = path.join(hooksDir, handlerFile);
743
786
  return `NEXO_HOME=${nexoHome} python3 ${runtimePath}`;
@@ -795,7 +838,7 @@ function registerAllCoreHooks(settings, hooksDir, nexoHome) {
795
838
 
796
839
  // Ensure operations dir exists for any hook that wants to drop a file there
797
840
  // (session-start.py writes .session-start-ts here).
798
- fs.mkdirSync(path.join(nexoHome, "operations"), { recursive: true });
841
+ fs.mkdirSync(path.join(nexoHome, "runtime", "operations"), { recursive: true });
799
842
 
800
843
  const manifestEntries = _manifestHookEntries();
801
844
  const registrations = [];
@@ -909,7 +952,7 @@ function registerAllCoreHooks(settings, hooksDir, nexoHome) {
909
952
  * NEVER overwrites an existing schedule.json (user customization).
910
953
  */
911
954
  function loadOrCreateSchedule(nexoHome) {
912
- const configDir = path.join(nexoHome, "config");
955
+ const configDir = resolveRuntimeConfigDir(nexoHome);
913
956
  fs.mkdirSync(configDir, { recursive: true });
914
957
  const scheduleFile = path.join(configDir, "schedule.json");
915
958
 
@@ -988,6 +1031,71 @@ function getDefaultSchedule(timezone) {
988
1031
  };
989
1032
  }
990
1033
 
1034
+ function writeDesktopProductMode(nexoHome) {
1035
+ if (String(process.env.NEXO_DESKTOP_MANAGED || "").trim() !== "1") return;
1036
+ const configDir = resolveRuntimeConfigDir(nexoHome);
1037
+ fs.mkdirSync(configDir, { recursive: true });
1038
+ const target = path.join(configDir, "product-mode.json");
1039
+ let createdAt = new Date().toISOString();
1040
+ if (fs.existsSync(target)) {
1041
+ try {
1042
+ const existing = JSON.parse(fs.readFileSync(target, "utf8"));
1043
+ if (existing && typeof existing === "object" && existing.created_at) {
1044
+ createdAt = existing.created_at;
1045
+ }
1046
+ } catch (_) {}
1047
+ }
1048
+ fs.writeFileSync(target, JSON.stringify({
1049
+ desktop_managed: true,
1050
+ product_mode: "desktop_closed_product",
1051
+ disabled_features: ["evolution"],
1052
+ source: "desktop",
1053
+ created_at: createdAt,
1054
+ updated_at: new Date().toISOString(),
1055
+ }, null, 2));
1056
+ }
1057
+
1058
+ function ensureEvolutionObjectiveForCurrentProductMode(nexoHome) {
1059
+ const brainDir = resolveRuntimeBrainDir(nexoHome);
1060
+ fs.mkdirSync(brainDir, { recursive: true });
1061
+ const evoObjectivePath = path.join(brainDir, "evolution-objective.json");
1062
+ const desktopManaged = String(process.env.NEXO_DESKTOP_MANAGED || "").trim() === "1";
1063
+ let payload = null;
1064
+ if (fs.existsSync(evoObjectivePath)) {
1065
+ try {
1066
+ payload = JSON.parse(fs.readFileSync(evoObjectivePath, "utf8"));
1067
+ } catch (_) {
1068
+ payload = null;
1069
+ }
1070
+ }
1071
+ if (!payload || typeof payload !== "object") {
1072
+ payload = {
1073
+ objective: "Improve operational excellence and reduce repeated errors",
1074
+ focus_areas: ["error_prevention", "proactivity", "memory_quality"],
1075
+ evolution_enabled: true,
1076
+ evolution_mode: "auto",
1077
+ dimensions: {
1078
+ episodic_memory: { current: 0, target: 90 },
1079
+ autonomy: { current: 0, target: 80 },
1080
+ proactivity: { current: 0, target: 70 },
1081
+ self_improvement: { current: 0, target: 60 },
1082
+ agi: { current: 0, target: 20 },
1083
+ },
1084
+ total_evolutions: 0,
1085
+ consecutive_failures: 0,
1086
+ created_at: new Date().toISOString(),
1087
+ };
1088
+ }
1089
+ if (desktopManaged) {
1090
+ payload.evolution_enabled = false;
1091
+ payload.disabled_reason = "Disabled by NEXO Desktop product contract";
1092
+ payload.disabled_by = "desktop_product";
1093
+ payload.desktop_managed = true;
1094
+ }
1095
+ fs.writeFileSync(evoObjectivePath, JSON.stringify(payload, null, 2));
1096
+ return evoObjectivePath;
1097
+ }
1098
+
991
1099
  function normalizePublicContributionConfig(config = {}) {
992
1100
  const base = getDefaultSchedule().public_contribution;
993
1101
  const merged = { ...base, ...(config || {}) };
@@ -1448,7 +1556,7 @@ async function maybeConfigurePowerPolicy(schedule, useDefaults) {
1448
1556
  schedule.power_policy = "disabled";
1449
1557
  }
1450
1558
  schedule.power_policy_version = 2;
1451
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
1559
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
1452
1560
  return schedule;
1453
1561
  }
1454
1562
 
@@ -1471,13 +1579,13 @@ async function maybeConfigurePublicContribution(schedule, useDefaults) {
1471
1579
  const current = normalizePublicContributionConfig((schedule && schedule.public_contribution) || {});
1472
1580
  if (current.mode && current.mode !== "unset") {
1473
1581
  schedule.public_contribution = current;
1474
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
1582
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
1475
1583
  return schedule;
1476
1584
  }
1477
1585
 
1478
1586
  if (useDefaults || !process.stdin.isTTY || !process.stdout.isTTY) {
1479
1587
  schedule.public_contribution = current;
1480
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
1588
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
1481
1589
  return schedule;
1482
1590
  }
1483
1591
 
@@ -1526,7 +1634,7 @@ async function maybeConfigurePublicContribution(schedule, useDefaults) {
1526
1634
  }
1527
1635
 
1528
1636
  schedule.public_contribution = current;
1529
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
1637
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
1530
1638
  return schedule;
1531
1639
  }
1532
1640
 
@@ -1561,12 +1669,12 @@ const DAY_MAP = {
1561
1669
  function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir, enabledOptionals = {}) {
1562
1670
  const home = require("os").homedir();
1563
1671
  const nexoCode = nexoHome;
1564
- const logsDir = path.join(nexoHome, "logs");
1672
+ const logsDir = path.join(nexoHome, "runtime", "logs");
1565
1673
  fs.mkdirSync(logsDir, { recursive: true });
1566
1674
 
1567
- // Resolve script path: "root" means NEXO_HOME directly, "scripts" means NEXO_HOME/scripts/
1675
+ // Resolve script path against the canonical runtime code tree.
1568
1676
  function scriptPath(proc) {
1569
- const dir = proc.scriptDir === "root" ? nexoHome : path.join(nexoHome, "scripts");
1677
+ const dir = proc.scriptDir === "root" ? runtimeCodeDir(nexoHome) : runtimeScriptsDir(nexoHome);
1570
1678
  return path.join(dir, proc.script);
1571
1679
  }
1572
1680
 
@@ -1916,7 +2024,7 @@ async function main() {
1916
2024
 
1917
2025
  // Update hooks (entire directory)
1918
2026
  const hooksSrc = path.join(srcDir, "hooks");
1919
- const hooksDest = path.join(NEXO_HOME, "hooks");
2027
+ const hooksDest = path.join(NEXO_HOME, "core", "hooks");
1920
2028
  if (fs.existsSync(hooksSrc)) {
1921
2029
  copyDirRec(hooksSrc, hooksDest);
1922
2030
  // Make .sh files executable
@@ -1931,14 +2039,16 @@ async function main() {
1931
2039
  coreFlatFiles.forEach((f) => {
1932
2040
  const src = path.join(srcDir, f);
1933
2041
  if (fs.existsSync(src)) {
1934
- fs.copyFileSync(src, path.join(NEXO_HOME, f));
2042
+ const dest = path.join(NEXO_HOME, "core", f);
2043
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
2044
+ fs.copyFileSync(src, dest);
1935
2045
  }
1936
2046
  });
1937
2047
  // Update core packages (db/, cognitive/) — full directory copy
1938
2048
  getCoreRuntimePackages().forEach(pkg => {
1939
2049
  const pkgSrc = path.join(srcDir, pkg);
1940
2050
  if (fs.existsSync(pkgSrc)) {
1941
- copyDirRec(pkgSrc, path.join(NEXO_HOME, pkg));
2051
+ copyDirRec(pkgSrc, path.join(NEXO_HOME, "core", pkg));
1942
2052
  }
1943
2053
  });
1944
2054
  // Publish Brain contracts to ~/.nexo/brain/ (read by NEXO Desktop et al.)
@@ -1971,7 +2081,7 @@ async function main() {
1971
2081
 
1972
2082
  // Update plugins (all .py files in plugins/)
1973
2083
  const pluginsSrc = path.join(srcDir, "plugins");
1974
- const pluginsDest = path.join(NEXO_HOME, "plugins");
2084
+ const pluginsDest = path.join(NEXO_HOME, "core", "plugins");
1975
2085
  fs.mkdirSync(pluginsDest, { recursive: true });
1976
2086
  if (fs.existsSync(pluginsSrc)) {
1977
2087
  fs.readdirSync(pluginsSrc).filter(f => f.endsWith(".py") && !isDuplicateArtifactName(f, pluginsSrc)).forEach((f) => {
@@ -1982,7 +2092,7 @@ async function main() {
1982
2092
 
1983
2093
  // Update dashboard (recursive — includes static/, templates/)
1984
2094
  const dashSrc = path.join(srcDir, "dashboard");
1985
- const dashDest = path.join(NEXO_HOME, "dashboard");
2095
+ const dashDest = path.join(NEXO_HOME, "core", "dashboard");
1986
2096
  if (fs.existsSync(dashSrc)) {
1987
2097
  copyDirRec(dashSrc, dashDest);
1988
2098
  log(" Dashboard updated.");
@@ -1990,7 +2100,7 @@ async function main() {
1990
2100
 
1991
2101
  // Update rules (directory with core-rules.json, __init__.py, migrate.py)
1992
2102
  const rulesSrc = path.join(srcDir, "rules");
1993
- const rulesDest = path.join(NEXO_HOME, "rules");
2103
+ const rulesDest = path.join(NEXO_HOME, "core", "rules");
1994
2104
  if (fs.existsSync(rulesSrc)) {
1995
2105
  copyDirRec(rulesSrc, rulesDest);
1996
2106
  log(" Rules updated.");
@@ -1998,7 +2108,7 @@ async function main() {
1998
2108
 
1999
2109
  // Update crons (manifest.json + sync.py — needed by catchup & watchdog)
2000
2110
  const cronsMigSrc = path.join(srcDir, "crons");
2001
- const cronsMigDest = path.join(NEXO_HOME, "crons");
2111
+ const cronsMigDest = path.join(NEXO_HOME, "runtime", "crons");
2002
2112
  if (fs.existsSync(cronsMigSrc)) {
2003
2113
  copyDirRec(cronsMigSrc, cronsMigDest);
2004
2114
  log(" Crons updated.");
@@ -2006,7 +2116,7 @@ async function main() {
2006
2116
 
2007
2117
  // Update scripts (all .py, .sh files + subdirectories like deep-sleep/)
2008
2118
  const scriptsSrc = path.join(srcDir, "scripts");
2009
- const scriptsDest = path.join(NEXO_HOME, "scripts");
2119
+ const scriptsDest = path.join(NEXO_HOME, "core", "scripts");
2010
2120
  if (fs.existsSync(scriptsSrc)) {
2011
2121
  copyDirRec(scriptsSrc, scriptsDest);
2012
2122
  // Make .sh files executable
@@ -2023,7 +2133,7 @@ async function main() {
2023
2133
  try { settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS, "utf8")); } catch {}
2024
2134
  }
2025
2135
  if (!settings.hooks) settings.hooks = {};
2026
- const migHooksDest = path.join(NEXO_HOME, "hooks");
2136
+ const migHooksDest = path.join(NEXO_HOME, "core", "hooks");
2027
2137
  registerAllCoreHooks(settings, migHooksDest, NEXO_HOME);
2028
2138
  fs.mkdirSync(path.dirname(CLAUDE_SETTINGS), { recursive: true });
2029
2139
  fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
@@ -2037,7 +2147,7 @@ async function main() {
2037
2147
  migSchedule = await maybeConfigureFullDiskAccess(migSchedule, useDefaults, migPython);
2038
2148
  let migOptionals = {};
2039
2149
  try {
2040
- const optFile = path.join(NEXO_HOME, "config", "optionals.json");
2150
+ const optFile = path.join(resolveRuntimeConfigDir(NEXO_HOME), "optionals.json");
2041
2151
  if (fs.existsSync(optFile)) migOptionals = JSON.parse(fs.readFileSync(optFile, "utf8"));
2042
2152
  } catch {}
2043
2153
  installAllProcesses(platform, migPython, NEXO_HOME, migSchedule, LAUNCH_AGENTS, migOptionals);
@@ -2057,20 +2167,19 @@ async function main() {
2057
2167
  throw new Error(`F0.6 layout finalization failed: ${migLayoutFinalize.error}`);
2058
2168
  }
2059
2169
 
2060
- // Save updated CLAUDE.md template as reference (don't overwrite user's)
2170
+ // Keep the rendered template in-memory for version tracking, but do
2171
+ // not drop a loose reference file in NEXO_HOME root.
2061
2172
  const templateSrc = path.join(__dirname, "..", "templates", "CLAUDE.md.template");
2062
2173
  if (fs.existsSync(templateSrc)) {
2063
2174
  const operatorName = installed.operator_name || DEFAULT_ASSISTANT_NAME;
2064
2175
  let claudeMd = fs.readFileSync(templateSrc, "utf8")
2065
2176
  .replace(/\{\{NAME\}\}/g, operatorName)
2066
2177
  .replace(/\{\{NEXO_HOME\}\}/g, NEXO_HOME);
2067
- fs.writeFileSync(path.join(NEXO_HOME, "CLAUDE.md.updated"), claudeMd);
2068
- log(` Updated CLAUDE.md template saved to ~/.nexo/CLAUDE.md.updated`);
2069
2178
 
2070
2179
  // Update CLAUDE.md version tracker (auto_update.py will handle section migration on next server start)
2071
2180
  const migClaudeMdVerMatch = claudeMd.match(/nexo-claude-md-version:\s*([\d.]+)/);
2072
2181
  if (migClaudeMdVerMatch) {
2073
- const migDataDir = path.join(NEXO_HOME, "data");
2182
+ const migDataDir = resolveRuntimeDataDir(NEXO_HOME);
2074
2183
  fs.mkdirSync(migDataDir, { recursive: true });
2075
2184
  // Don't write the version yet — let auto_update.py detect the diff and migrate sections
2076
2185
  // Only write if no version file exists (first time with version tracking)
@@ -2130,7 +2239,7 @@ async function main() {
2130
2239
  }
2131
2240
 
2132
2241
  // Same version — backfill crons/ if missing (for installs before crons was shipped)
2133
- const cronsDest = path.join(NEXO_HOME, "crons");
2242
+ const cronsDest = resolveRuntimeCronsDir(NEXO_HOME);
2134
2243
  const cronsSrc = path.join(__dirname, "..", "src", "crons");
2135
2244
  if (fs.existsSync(cronsSrc)) {
2136
2245
  const copyDirRec2 = (src, dest) => {
@@ -2164,7 +2273,7 @@ async function main() {
2164
2273
  }
2165
2274
 
2166
2275
  // Same version — refresh packaged core skills/templates/runtime helpers too.
2167
- const skillsCoreDest = path.join(NEXO_HOME, "skills-core");
2276
+ const skillsCoreDest = path.join(NEXO_HOME, "core", "skills");
2168
2277
  const skillsCoreSrc = path.join(__dirname, "..", "src", "skills");
2169
2278
  if (fs.existsSync(skillsCoreSrc)) {
2170
2279
  const copyDirRec3 = (src, dest) => {
@@ -2183,8 +2292,9 @@ async function main() {
2183
2292
 
2184
2293
  ["skills_runtime.py"].forEach((fname) => {
2185
2294
  const srcFile = path.join(__dirname, "..", "src", fname);
2186
- const destFile = path.join(NEXO_HOME, fname);
2295
+ const destFile = path.join(NEXO_HOME, "core", fname);
2187
2296
  if (fs.existsSync(srcFile)) {
2297
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
2188
2298
  fs.copyFileSync(srcFile, destFile);
2189
2299
  }
2190
2300
  });
@@ -2615,10 +2725,11 @@ async function main() {
2615
2725
  calibrated_at: new Date().toISOString(),
2616
2726
  };
2617
2727
  // Ensure NEXO_HOME and brain dir exist before writing calibration
2728
+ const runtimeBrainDir = resolveRuntimeBrainDir(NEXO_HOME);
2618
2729
  fs.mkdirSync(NEXO_HOME, { recursive: true });
2619
- fs.mkdirSync(path.join(NEXO_HOME, "brain"), { recursive: true });
2730
+ fs.mkdirSync(runtimeBrainDir, { recursive: true });
2620
2731
  fs.writeFileSync(
2621
- path.join(NEXO_HOME, "brain", "calibration.json"),
2732
+ path.join(runtimeBrainDir, "calibration.json"),
2622
2733
  JSON.stringify(calibration, null, 2)
2623
2734
  );
2624
2735
 
@@ -2672,7 +2783,7 @@ async function main() {
2672
2783
  // Persist the updated calibration (auto_install may have changed post-write above).
2673
2784
  try {
2674
2785
  fs.writeFileSync(
2675
- path.join(NEXO_HOME, "brain", "calibration.json"),
2786
+ path.join(runtimeBrainDir, "calibration.json"),
2676
2787
  JSON.stringify(calibration, null, 2)
2677
2788
  );
2678
2789
  } catch (_) {}
@@ -2683,10 +2794,9 @@ async function main() {
2683
2794
  // the heavy bootstrap (client installs, pip, scan, LaunchAgents) so the
2684
2795
  // smoke does not sit on long dependency timeouts inside sandboxes.
2685
2796
  try {
2686
- const canonicalBrainDir = path.join(NEXO_HOME, "personal", "brain");
2687
- fs.mkdirSync(canonicalBrainDir, { recursive: true });
2797
+ fs.mkdirSync(runtimeBrainDir, { recursive: true });
2688
2798
  fs.writeFileSync(
2689
- path.join(canonicalBrainDir, "calibration.json"),
2799
+ path.join(runtimeBrainDir, "calibration.json"),
2690
2800
  JSON.stringify(calibration, null, 2)
2691
2801
  );
2692
2802
  } catch (_) {}
@@ -2746,40 +2856,33 @@ async function main() {
2746
2856
  const dirs = [
2747
2857
  NEXO_HOME,
2748
2858
  path.join(NEXO_HOME, "bin"),
2749
- path.join(NEXO_HOME, "plugins"),
2750
- path.join(NEXO_HOME, "scripts"),
2751
- path.join(NEXO_HOME, "skills"),
2752
- path.join(NEXO_HOME, "skills-core"),
2753
- path.join(NEXO_HOME, "skills-runtime"),
2754
- path.join(NEXO_HOME, "logs"),
2755
- path.join(NEXO_HOME, "backups"),
2756
- path.join(NEXO_HOME, "coordination"),
2757
- path.join(NEXO_HOME, "brain"),
2758
- path.join(NEXO_HOME, "config"),
2759
- path.join(NEXO_HOME, "operations"),
2859
+ path.join(NEXO_HOME, "core"),
2860
+ path.join(NEXO_HOME, "core", "plugins"),
2861
+ path.join(NEXO_HOME, "core", "scripts"),
2862
+ path.join(NEXO_HOME, "core", "skills"),
2863
+ path.join(NEXO_HOME, "core", "hooks"),
2864
+ path.join(NEXO_HOME, "core", "rules"),
2865
+ path.join(NEXO_HOME, "core", "dashboard"),
2866
+ path.join(NEXO_HOME, "personal"),
2867
+ path.join(NEXO_HOME, "personal", "brain"),
2868
+ path.join(NEXO_HOME, "personal", "config"),
2869
+ path.join(NEXO_HOME, "personal", "skills"),
2870
+ path.join(NEXO_HOME, "runtime"),
2871
+ path.join(NEXO_HOME, "runtime", "data"),
2872
+ path.join(NEXO_HOME, "runtime", "logs"),
2873
+ path.join(NEXO_HOME, "runtime", "backups"),
2874
+ path.join(NEXO_HOME, "runtime", "coordination"),
2875
+ path.join(NEXO_HOME, "runtime", "operations"),
2876
+ path.join(NEXO_HOME, "runtime", "crons"),
2760
2877
  ];
2761
2878
  dirs.forEach((d) => fs.mkdirSync(d, { recursive: true }));
2762
2879
 
2763
- // Create default evolution-objective.json in brain/ if it doesn't exist
2764
- const evoObjectivePath = path.join(NEXO_HOME, "brain", "evolution-objective.json");
2765
- if (!fs.existsSync(evoObjectivePath)) {
2766
- fs.writeFileSync(evoObjectivePath, JSON.stringify({
2767
- objective: "Improve operational excellence and reduce repeated errors",
2768
- focus_areas: ["error_prevention", "proactivity", "memory_quality"],
2769
- evolution_enabled: true,
2770
- evolution_mode: "auto",
2771
- dimensions: {
2772
- episodic_memory: { current: 0, target: 90 },
2773
- autonomy: { current: 0, target: 80 },
2774
- proactivity: { current: 0, target: 70 },
2775
- self_improvement: { current: 0, target: 60 },
2776
- agi: { current: 0, target: 20 },
2777
- },
2778
- total_evolutions: 0,
2779
- consecutive_failures: 0,
2780
- created_at: new Date().toISOString(),
2781
- }, null, 2));
2782
- log(" Created default evolution-objective.json in brain/");
2880
+ writeDesktopProductMode(NEXO_HOME);
2881
+ const evoObjectivePath = ensureEvolutionObjectiveForCurrentProductMode(NEXO_HOME);
2882
+ if (String(process.env.NEXO_DESKTOP_MANAGED || "").trim() === "1") {
2883
+ log(" Desktop product contract detected — evolution disabled.");
2884
+ } else if (fs.existsSync(evoObjectivePath)) {
2885
+ log(" Ensured evolution-objective.json in brain/");
2783
2886
  }
2784
2887
 
2785
2888
  // Write version file for auto-update tracking
@@ -2825,7 +2928,9 @@ async function main() {
2825
2928
  coreFiles.forEach((f) => {
2826
2929
  const src = path.join(srcDir, f);
2827
2930
  if (fs.existsSync(src)) {
2828
- fs.copyFileSync(src, path.join(NEXO_HOME, f));
2931
+ const dest = path.join(NEXO_HOME, "core", f);
2932
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
2933
+ fs.copyFileSync(src, dest);
2829
2934
  }
2830
2935
  });
2831
2936
 
@@ -2840,6 +2945,7 @@ async function main() {
2840
2945
  `RUNTIME_HOME="${NEXO_HOME}"`,
2841
2946
  'NEXO_HOME="$RUNTIME_HOME"',
2842
2947
  'export NEXO_HOME',
2948
+ 'export PYTHONDONTWRITEBYTECODE=1',
2843
2949
  'resolve_code_dir() {',
2844
2950
  ' if [ -n "${NEXO_CODE:-}" ] && [ -f "${NEXO_CODE%/}/cli.py" ]; then',
2845
2951
  ' printf \'%s\\n\' "${NEXO_CODE%/}"',
@@ -2924,24 +3030,24 @@ async function main() {
2924
3030
  getCoreRuntimePackages().forEach(pkg => {
2925
3031
  const pkgSrc = path.join(srcDir, pkg);
2926
3032
  if (fs.existsSync(pkgSrc)) {
2927
- copyDirRecursive(pkgSrc, path.join(NEXO_HOME, pkg));
3033
+ copyDirRecursive(pkgSrc, path.join(NEXO_HOME, "core", pkg));
2928
3034
  }
2929
3035
  });
2930
3036
 
2931
3037
  log("Copying plugins, scripts, and templates...");
2932
3038
  // Plugins (all .py files in plugins/)
2933
- fs.mkdirSync(path.join(NEXO_HOME, "plugins"), { recursive: true });
3039
+ fs.mkdirSync(path.join(NEXO_HOME, "core", "plugins"), { recursive: true });
2934
3040
  if (fs.existsSync(pluginsSrcDir)) {
2935
3041
  fs.readdirSync(pluginsSrcDir).filter(f => f.endsWith(".py") && !isDuplicateArtifactName(f, pluginsSrcDir)).forEach((f) => {
2936
- fs.copyFileSync(path.join(pluginsSrcDir, f), path.join(NEXO_HOME, "plugins", f));
3042
+ fs.copyFileSync(path.join(pluginsSrcDir, f), path.join(NEXO_HOME, "core", "plugins", f));
2937
3043
  });
2938
3044
  }
2939
3045
 
2940
3046
  // Scripts (all files + subdirectories like deep-sleep/)
2941
3047
  if (fs.existsSync(scriptsSrcDir)) {
2942
- copyDirRecursive(scriptsSrcDir, path.join(NEXO_HOME, "scripts"));
3048
+ copyDirRecursive(scriptsSrcDir, path.join(NEXO_HOME, "core", "scripts"));
2943
3049
  // Make .sh files executable
2944
- const scriptsDest = path.join(NEXO_HOME, "scripts");
3050
+ const scriptsDest = path.join(NEXO_HOME, "core", "scripts");
2945
3051
  fs.readdirSync(scriptsDest).filter(f => f.endsWith(".sh")).forEach(f => {
2946
3052
  fs.chmodSync(path.join(scriptsDest, f), "755");
2947
3053
  });
@@ -2950,28 +3056,28 @@ async function main() {
2950
3056
 
2951
3057
  // Core skills are shipped separately from personal skills.
2952
3058
  if (fs.existsSync(skillsSrcDir)) {
2953
- copyDirRecursive(skillsSrcDir, path.join(NEXO_HOME, "skills-core"));
3059
+ copyDirRecursive(skillsSrcDir, path.join(NEXO_HOME, "core", "skills"));
2954
3060
  log(" Core skills installed.");
2955
3061
  }
2956
3062
 
2957
3063
  // Dashboard (recursive — includes static/, templates/)
2958
3064
  const dashSrcDir = path.join(srcDir, "dashboard");
2959
3065
  if (fs.existsSync(dashSrcDir)) {
2960
- copyDirRecursive(dashSrcDir, path.join(NEXO_HOME, "dashboard"));
3066
+ copyDirRecursive(dashSrcDir, path.join(NEXO_HOME, "core", "dashboard"));
2961
3067
  log(" Dashboard installed.");
2962
3068
  }
2963
3069
 
2964
3070
  // Rules directory
2965
3071
  const rulesSrcDir = path.join(srcDir, "rules");
2966
3072
  if (fs.existsSync(rulesSrcDir)) {
2967
- copyDirRecursive(rulesSrcDir, path.join(NEXO_HOME, "rules"));
3073
+ copyDirRecursive(rulesSrcDir, path.join(NEXO_HOME, "core", "rules"));
2968
3074
  log(" Rules installed.");
2969
3075
  }
2970
3076
 
2971
3077
  // Crons directory (manifest.json + sync.py — needed by catchup & watchdog)
2972
3078
  const cronsSrcDir = path.join(srcDir, "crons");
2973
3079
  if (fs.existsSync(cronsSrcDir)) {
2974
- copyDirRecursive(cronsSrcDir, path.join(NEXO_HOME, "crons"));
3080
+ copyDirRecursive(cronsSrcDir, path.join(NEXO_HOME, "runtime", "crons"));
2975
3081
  log(" Crons installed.");
2976
3082
  }
2977
3083
 
@@ -3001,7 +3107,7 @@ async function main() {
3001
3107
  // Hooks directory
3002
3108
  const hooksSrcDir = path.join(srcDir, "hooks");
3003
3109
  if (fs.existsSync(hooksSrcDir)) {
3004
- const hooksDest = path.join(NEXO_HOME, "hooks");
3110
+ const hooksDest = path.join(NEXO_HOME, "core", "hooks");
3005
3111
  copyDirRecursive(hooksSrcDir, hooksDest);
3006
3112
  // Make .sh files executable
3007
3113
  fs.readdirSync(hooksDest).filter(f => f.endsWith(".sh")).forEach(f => {
@@ -3028,7 +3134,7 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
3028
3134
  - Give long explanations when a short answer suffices
3029
3135
  - Repeat mistakes I've already logged
3030
3136
  `;
3031
- fs.writeFileSync(path.join(NEXO_HOME, "brain", "personality.md"), personality);
3137
+ fs.writeFileSync(path.join(resolveRuntimeBrainDir(NEXO_HOME), "personality.md"), personality);
3032
3138
 
3033
3139
  // Deep scan (P9) — comprehensive environment analysis
3034
3140
  const profileData = {
@@ -3517,15 +3623,15 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
3517
3623
 
3518
3624
  // Save full profile
3519
3625
  fs.writeFileSync(
3520
- path.join(NEXO_HOME, "brain", "profile.json"),
3626
+ path.join(resolveRuntimeBrainDir(NEXO_HOME), "profile.json"),
3521
3627
  JSON.stringify(profileData, null, 2)
3522
3628
  );
3523
- log(`Saved to ~/.nexo/brain/profile.json`);
3629
+ log(`Saved to ${path.join(resolveRuntimeBrainDir(NEXO_HOME), "profile.json")}`);
3524
3630
 
3525
3631
  } else {
3526
3632
  // No scan — save minimal profile
3527
3633
  fs.writeFileSync(
3528
- path.join(NEXO_HOME, "brain", "profile.json"),
3634
+ path.join(resolveRuntimeBrainDir(NEXO_HOME), "profile.json"),
3529
3635
  JSON.stringify(profileData, null, 2)
3530
3636
  );
3531
3637
  log(lang === "es" ? "Sin problema. Iré aprendiéndote sobre la marcha." : "No problem. I'll learn about you as we go.");
@@ -3552,7 +3658,7 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
3552
3658
  ## Work patterns
3553
3659
  (${operatorName} will observe and record these)
3554
3660
  `;
3555
- fs.writeFileSync(path.join(NEXO_HOME, "brain", "user-profile.md"), profileMd);
3661
+ fs.writeFileSync(path.join(resolveRuntimeBrainDir(NEXO_HOME), "user-profile.md"), profileMd);
3556
3662
 
3557
3663
  console.log("");
3558
3664
 
@@ -3647,7 +3753,7 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
3647
3753
  log("Setting up automated processes...");
3648
3754
  let schedule = loadOrCreateSchedule(NEXO_HOME);
3649
3755
  schedule = applyClientSetupToSchedule(schedule, clientSetup);
3650
- fs.writeFileSync(path.join(NEXO_HOME, "config", "schedule.json"), JSON.stringify(schedule, null, 2));
3756
+ fs.writeFileSync(resolveRuntimeSchedulePath(NEXO_HOME), JSON.stringify(schedule, null, 2));
3651
3757
  schedule = await maybeConfigurePowerPolicy(schedule, useDefaults);
3652
3758
  schedule = await maybeConfigurePublicContribution(schedule, useDefaults);
3653
3759
  schedule = await maybeConfigureFullDiskAccess(schedule, useDefaults, python);
@@ -3662,7 +3768,7 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
3662
3768
 
3663
3769
  // Persist optional process preferences for auto-update
3664
3770
  try {
3665
- const configDir = path.join(NEXO_HOME, "config");
3771
+ const configDir = resolveRuntimeConfigDir(NEXO_HOME);
3666
3772
  fs.mkdirSync(configDir, { recursive: true });
3667
3773
  const optFile = path.join(configDir, "optionals.json");
3668
3774
  fs.writeFileSync(optFile, JSON.stringify(enabledOptionals, null, 2));
@@ -3769,7 +3875,7 @@ See ~/.nexo/ for configuration.
3769
3875
  // Write initial CLAUDE.md version tracker
3770
3876
  const claudeMdVersionMatch = claudeMd.match(/nexo-claude-md-version:\s*([\d.]+)/);
3771
3877
  if (claudeMdVersionMatch) {
3772
- const dataDir = path.join(NEXO_HOME, "data");
3878
+ const dataDir = resolveRuntimeDataDir(NEXO_HOME);
3773
3879
  fs.mkdirSync(dataDir, { recursive: true });
3774
3880
  fs.writeFileSync(path.join(dataDir, "claude_md_version.txt"), claudeMdVersionMatch[1]);
3775
3881
  log(`CLAUDE.md version tracker initialized: v${claudeMdVersionMatch[1]}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "7.1.1",
3
+ "version": "7.1.2",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO Brain \u2014 Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
6
6
  "homepage": "https://nexo-brain.com",