nexo-brain 7.13.7 → 7.13.8

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "7.13.7",
3
+ "version": "7.13.8",
4
4
  "description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
5
5
  "author": {
6
6
  "name": "NEXO Brain",
package/README.md CHANGED
@@ -18,7 +18,9 @@
18
18
 
19
19
  [Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
20
20
 
21
- Version `7.13.7` is the current packaged-runtime line. Patch release over v7.13.6 — Brain adds an authenticated official protocol-card client (`nexo_card_catalog`, `nexo_card_get`, `nexo_card_match`) so agents can ask the NEXO Desktop backend for the right task protocol at runtime. The protocol corpus stays private on the server; this open-source package ships only the client, tool map, and agent guidance.
21
+ Version `7.13.8` is the current packaged-runtime line. Patch release over v7.13.7 — Brain now rejects Python <3.10 during Desktop-managed fresh installs, honors the Python interpreter prepared by Desktop, and fails clearly before dependency resolution if an unsupported Apple Python 3.9 reaches the installer.
22
+
23
+ Previously in `7.13.7`: patch release — Brain adds an authenticated official protocol-card client (`nexo_card_catalog`, `nexo_card_get`, `nexo_card_match`) so agents can ask the NEXO Desktop backend for the right task protocol at runtime. The protocol corpus stays private on the server; this open-source package ships only the client, tool map, and agent guidance.
22
24
 
23
25
  Previously in `7.13.6`: patch release — Codex hook sync now renders the managed `PreToolUse` shell/exec_command guard with native Windows `cmd.exe` syntax while preserving the existing POSIX command on macOS/Linux. Result: coordinated Desktop bundles can ship the fixed Brain without changing the Mac/Windows installation contract.
24
26
 
package/bin/nexo-brain.js CHANGED
@@ -36,6 +36,8 @@ if (process.platform === "win32") {
36
36
  let NEXO_HOME = process.env.NEXO_HOME || path.join(require("os").homedir(), ".nexo");
37
37
  const DEFAULT_ASSISTANT_NAME = "Nova";
38
38
  const RESERVED_ASSISTANT_NAME_KEYS = new Set(["nexo", "nexobrain", "nexodesktop"]);
39
+ const MIN_INSTALLER_PYTHON_MAJOR = 3;
40
+ const MIN_INSTALLER_PYTHON_MINOR = 10;
39
41
 
40
42
  function normalizeAssistantNameCandidate(value) {
41
43
  return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
@@ -225,6 +227,57 @@ function run(cmd, opts = {}) {
225
227
  }
226
228
  }
227
229
 
230
+ function shSingleQuote(value) {
231
+ return "'" + String(value || "").replace(/'/g, "'\\''") + "'";
232
+ }
233
+
234
+ function runPythonProbe(pythonBin, args, timeout = 15000) {
235
+ if (!pythonBin) return null;
236
+ try {
237
+ const result = spawnSync(pythonBin, args, {
238
+ encoding: "utf8",
239
+ stdio: ["ignore", "pipe", "pipe"],
240
+ timeout,
241
+ });
242
+ if (result.status !== 0) return null;
243
+ return String(result.stdout || result.stderr || "").trim();
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+
249
+ function pythonVersion(pythonBin) {
250
+ return runPythonProbe(pythonBin, ["-c", "import sys; print(sys.version.split()[0])"]);
251
+ }
252
+
253
+ function pythonVersionMeetsMinimum(versionText) {
254
+ const match = String(versionText || "").trim().match(/^(\d+)\.(\d+)(?:\.|$)/);
255
+ if (!match) return false;
256
+ const major = Number(match[1]);
257
+ const minor = Number(match[2]);
258
+ return major > MIN_INSTALLER_PYTHON_MAJOR
259
+ || (major === MIN_INSTALLER_PYTHON_MAJOR && minor >= MIN_INSTALLER_PYTHON_MINOR);
260
+ }
261
+
262
+ function resolveInstallerPython() {
263
+ const candidates = [
264
+ process.env.NEXO_BOOTSTRAP_PYTHON,
265
+ process.env.NEXO_RUNTIME_PYTHON,
266
+ process.env.NEXO_PYTHON,
267
+ run("which python3"),
268
+ run("which python"),
269
+ ].filter(Boolean);
270
+ const seen = new Set();
271
+ for (const candidate of candidates) {
272
+ const clean = String(candidate || "").trim();
273
+ if (!clean || seen.has(clean)) continue;
274
+ seen.add(clean);
275
+ const version = pythonVersion(clean);
276
+ if (version && pythonVersionMeetsMinimum(version)) return clean;
277
+ }
278
+ return "";
279
+ }
280
+
228
281
  function findBundledWheel(wheelsDir, prefix) {
229
282
  try {
230
283
  const normalizedPrefix = String(prefix || "").toLowerCase() + "-";
@@ -1710,6 +1763,48 @@ function buildManagedCliEnv(extraEnv = {}) {
1710
1763
  };
1711
1764
  }
1712
1765
 
1766
+ function ensureDesktopNodeShim(desktopNode) {
1767
+ const clean = String(desktopNode || "").trim();
1768
+ if (!clean) return "";
1769
+ const shimDir = path.join(NEXO_HOME, "runtime", "bootstrap", "node-shim");
1770
+ fs.mkdirSync(shimDir, { recursive: true });
1771
+ if (process.platform === "win32") {
1772
+ const shimPath = path.join(shimDir, "node.cmd");
1773
+ fs.writeFileSync(
1774
+ shimPath,
1775
+ `@echo off\r\nset ELECTRON_RUN_AS_NODE=1\r\n"${clean}" %*\r\n`,
1776
+ );
1777
+ return shimDir;
1778
+ }
1779
+ const shimPath = path.join(shimDir, "node");
1780
+ fs.writeFileSync(
1781
+ shimPath,
1782
+ [
1783
+ "#!/bin/sh",
1784
+ "export ELECTRON_RUN_AS_NODE=1",
1785
+ `exec ${shSingleQuote(clean)} "$@"`,
1786
+ "",
1787
+ ].join("\n"),
1788
+ );
1789
+ fs.chmodSync(shimPath, 0o755);
1790
+ return shimDir;
1791
+ }
1792
+
1793
+ function withDesktopNodeShim(env, desktopNode) {
1794
+ try {
1795
+ const shimDir = ensureDesktopNodeShim(desktopNode);
1796
+ if (!shimDir) return env;
1797
+ return {
1798
+ ...env,
1799
+ ELECTRON_RUN_AS_NODE: "1",
1800
+ PATH: [shimDir, env.PATH || ""].filter(Boolean).join(path.delimiter),
1801
+ };
1802
+ } catch (err) {
1803
+ log(`Desktop Node shim could not be created: ${String(err && err.message || err)}`);
1804
+ return env;
1805
+ }
1806
+ }
1807
+
1713
1808
  function resolveManagedClaudeBinary() {
1714
1809
  const prefix = managedClaudePrefix();
1715
1810
  const candidates = process.platform === "win32"
@@ -1921,11 +2016,13 @@ function installClaudeCodeCli(platform) {
1921
2016
  return { installed: true, path: claudeInstalled };
1922
2017
  }
1923
2018
 
1924
- const installEnv = buildManagedCliEnv();
1925
2019
  const desktopNode = String(process.env.NEXO_DESKTOP_NODE || "").trim();
1926
2020
  const bundledNpmCli = String(process.env.NEXO_DESKTOP_NPM_CLI || "").trim();
1927
2021
  const managedPrefix = managedClaudePrefix();
1928
2022
  const desktopManaged = isDesktopManagedInstall();
2023
+ const npmViaDesktop = desktopNode && bundledNpmCli;
2024
+ let installEnv = buildManagedCliEnv();
2025
+ if (desktopNode) installEnv = withDesktopNodeShim(installEnv, desktopNode);
1929
2026
 
1930
2027
  // OFFLINE-FIRST v0.32.4: install claude-code wrapper + ALL its native packs
1931
2028
  // from bundled tarballs. Path: resources/brain-bundle/claude-code/*.tgz.
@@ -1963,8 +2060,18 @@ function installClaudeCodeCli(platform) {
1963
2060
  const tgzPaths = [path.join(bundledClaudeDir, wrapper), ...nativePacks.map((p) => path.join(bundledClaudeDir, p))];
1964
2061
  log(" Installing claude-code from bundled tarballs (offline, " + (1 + nativePacks.length) + " packs)...");
1965
2062
  spawnSync(
1966
- "npm",
1967
- ["install", "-g", "--prefix", managedPrefix, "--offline", "--no-audit", "--no-fund", ...tgzPaths],
2063
+ npmViaDesktop ? desktopNode : "npm",
2064
+ [
2065
+ ...(npmViaDesktop ? [bundledNpmCli] : []),
2066
+ "install",
2067
+ "-g",
2068
+ "--prefix",
2069
+ managedPrefix,
2070
+ "--offline",
2071
+ "--no-audit",
2072
+ "--no-fund",
2073
+ ...tgzPaths,
2074
+ ],
1968
2075
  { stdio: "inherit", env: installEnv },
1969
2076
  );
1970
2077
  claudeInstalled = detectInstalledClients().claude_code.path || "";
@@ -1977,8 +2084,15 @@ function installClaudeCodeCli(platform) {
1977
2084
  const tgzPath = path.join(bundledClaudeDir, wrapper);
1978
2085
  log(" Installing claude-code from bundled wrapper only (legacy bundle, may need network for native pack)...");
1979
2086
  spawnSync(
1980
- "npm",
1981
- ["install", "-g", "--prefix", managedPrefix, tgzPath],
2087
+ npmViaDesktop ? desktopNode : "npm",
2088
+ [
2089
+ ...(npmViaDesktop ? [bundledNpmCli] : []),
2090
+ "install",
2091
+ "-g",
2092
+ "--prefix",
2093
+ managedPrefix,
2094
+ tgzPath,
2095
+ ],
1982
2096
  { stdio: "inherit", env: installEnv },
1983
2097
  );
1984
2098
  claudeInstalled = detectInstalledClients().claude_code.path || "";
@@ -3081,7 +3195,7 @@ async function runSetup() {
3081
3195
  }
3082
3196
 
3083
3197
  // Find or install Python (platform-aware)
3084
- let python = run("which python3");
3198
+ let python = resolveInstallerPython();
3085
3199
  if (!python) {
3086
3200
  if (platform === "darwin") {
3087
3201
  // v0.32.5 — Mac vanilla NO trae python3. La auto-instalación de
@@ -3122,7 +3236,7 @@ async function runSetup() {
3122
3236
  // fallan al import. Pinning a `python@3.12` evita el drift.
3123
3237
  log("Python 3.12 not found. Installing via Homebrew...");
3124
3238
  spawnSync("brew", ["install", "python@3.12"], { stdio: "inherit" });
3125
- python = run("which python3.12") || run("which python3");
3239
+ python = resolveInstallerPython() || run("which python3.12") || run("which python3");
3126
3240
  }
3127
3241
  } else if (platform === "linux") {
3128
3242
  // Linux: try apt or yum
@@ -3132,7 +3246,7 @@ async function runSetup() {
3132
3246
  } else if (run("which yum")) {
3133
3247
  spawnSync("sudo", ["yum", "install", "-y", "python3", "python3-pip"], { stdio: "inherit" });
3134
3248
  }
3135
- python = run("which python3");
3249
+ python = resolveInstallerPython();
3136
3250
  }
3137
3251
  if (!python) {
3138
3252
  log("Python 3 not found and couldn't install automatically.");
@@ -3140,7 +3254,13 @@ async function runSetup() {
3140
3254
  process.exit(1);
3141
3255
  }
3142
3256
  }
3143
- const pyVersion = run(`${python} --version`);
3257
+ const pyVersion = pythonVersion(python);
3258
+ if (!pyVersion || !pythonVersionMeetsMinimum(pyVersion)) {
3259
+ log(pyVersion
3260
+ ? `Python at ${python} is ${pyVersion}; NEXO Brain requires Python >=${MIN_INSTALLER_PYTHON_MAJOR}.${MIN_INSTALLER_PYTHON_MINOR}.`
3261
+ : `Python at ${python || "(not found)"} is not executable.`);
3262
+ process.exit(1);
3263
+ }
3144
3264
  log(`Found ${pyVersion} at ${python}`);
3145
3265
  logMacPermissionsNotice(NEXO_HOME, python);
3146
3266
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "7.13.7",
3
+ "version": "7.13.8",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO Brain — 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",