nexo-brain 6.3.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "6.3.0",
3
+ "version": "6.5.0",
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,16 @@
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 `6.3.0` is the current packaged-runtime line — Plan Consolidado wave 2, coordinated with NEXO Desktop v0.18.0. Closes the remaining Guardian roadmap items that do not require an invasive structure migration: extended `cognitive_sentiment` shape (is_correction/valence/intent), extended `entities` schema, 21 labelled rule fixtures with R13 spike gates, Fase F telemetry loops + Deep Sleep phase, pinned local zero-shot classifier skeleton (mDeBERTa), hook respects `NEXO_MIGRATING=1`, `origin` column on `personal_scripts`, and the T4 LLM gate wrapping R15/R23e/R23f/R23h (byte-parity Py JS). Two pre-release auditors flagged a CRITICAL in the first JS wire (method-name + async mismatch) and a HIGH (classifier bool conflated "no" with "unparseable"); both corrected with regression tests before merge.
21
+ Version `6.5.0` is the current packaged-runtime line — Plan Consolidado fase F0.2: operators can now `nexo scripts enable|disable|status <name>` any personal automation. The cron wrapper honours the flag at every tick (`exit 0` with `summary='[disabled]'` while the LaunchAgent stays loaded). The companion NEXO Desktop client (a closed-source product, distributed separately) wires the same toggle into its Automatizaciones panel. See [CHANGELOG](CHANGELOG.md) for the full diff.
22
+
23
+ > **About NEXO Desktop.** NEXO Desktop is a separate closed-source companion app distributed at [systeam.es/nexo-desktop](https://systeam.es/nexo-desktop) — its source does not live in this repo. When release notes mention Desktop they describe a coordinated client release that consumes the Brain's CLI / MCP contract; the Brain itself is fully usable on its own (terminal, Codex, Claude Code, or any MCP client).
24
+
25
+
26
+ Previously in `6.4.0`: Plan Consolidado fase F1 — multi-tenant email accounts (`email_accounts` table, `nexo email setup` interactive wizard, `nexo email add --password-stdin --json` for machine consumers, idempotent migrator from legacy `~/.nexo/nexo-email/config.json`).
27
+
28
+ Previously in `6.3.1`: privacy hotfix over v6.3.0. The nightly auditor caught that `src/presets/entities_universal.json` in v6.3.0 shipped operator-specific `vhost_mapping` entries (private IPs, hostnames, tenant names). v6.3.1 pulls those out into `src/presets/entities_local.sample.json` (template) + `.gitignore`'d `~/.nexo/brain/presets/entities_local.json` (operator copy), and the installer drops the sample at `nexo init`. No behaviour change on the Guardian side.
29
+
30
+ Previously in `6.3.0` — Plan Consolidado wave 2, coordinated with NEXO Desktop v0.18.0. Closes the remaining Guardian roadmap items that do not require an invasive structure migration: extended `cognitive_sentiment` shape (is_correction/valence/intent), extended `entities` schema, 21 labelled rule fixtures with R13 spike gates, Fase F telemetry loops + Deep Sleep phase, pinned local zero-shot classifier skeleton (mDeBERTa), hook respects `NEXO_MIGRATING=1`, `origin` column on `personal_scripts`, and the T4 LLM gate wrapping R15/R23e/R23f/R23h (byte-parity Py ↔ JS). Two pre-release auditors flagged a CRITICAL in the first JS wire (method-name + async mismatch) and a HIGH (classifier bool conflated "no" with "unparseable"); both corrected with regression tests before merge.
22
31
 
23
32
  Previously in `6.1.1`: small fix to `nexo --help` so the `Latest: vX` line reliably appears when NEXO Desktop invokes the CLI via subprocess — unblocks the Desktop Brain auto-update banner that previously couldn't parse the version delta. No behaviour change for interactive terminal users; the 6-hour registry cache still rate-limits network calls. Bundles all v6.1.0 Protocol Enforcer Fase 2 + multi-claude-sid hotfix content.
24
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "6.3.0",
3
+ "version": "6.5.0",
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",
@@ -1519,12 +1519,56 @@ def _run_db_migrations() -> bool:
1519
1519
  applied = run_migrations(conn)
1520
1520
  if applied > 0:
1521
1521
  _log(f"Applied {applied} DB migration(s)")
1522
+ # Plan Consolidado F1 — one-shot legacy email config migration.
1523
+ # After m46 adds the table, operators installed pre-v6.4.0 still
1524
+ # keep their data inside ~/.nexo/nexo-email/config.json. If the
1525
+ # table is empty and the JSON exists, port the primary account
1526
+ # over automatically so scripts pick up the new source on the
1527
+ # next cron without the operator running anything manually.
1528
+ _maybe_migrate_legacy_email_config()
1522
1529
  return True
1523
1530
  except Exception as e:
1524
1531
  _log(f"DB migration error: {e}")
1525
1532
  return False
1526
1533
 
1527
1534
 
1535
+ def _maybe_migrate_legacy_email_config() -> None:
1536
+ """F1 auto-migrator — idempotent. Runs the helper script the first
1537
+ time after v6.4.0 lands on an existing runtime."""
1538
+ try:
1539
+ from db._email_accounts import get_email_account
1540
+ except Exception:
1541
+ return # m46 not applied yet (older runtime), nothing to do
1542
+ try:
1543
+ legacy = NEXO_HOME / "nexo-email" / "config.json"
1544
+ if not legacy.exists():
1545
+ return
1546
+ if get_email_account("primary"):
1547
+ return # already migrated
1548
+ import subprocess as _sp
1549
+ script = Path(__file__).resolve().parent / "scripts" / "nexo-email-migrate-config.py"
1550
+ if not script.exists():
1551
+ return
1552
+ _log("F1: migrating legacy email config.json → email_accounts table")
1553
+ env = {**os.environ, "PYTHONPATH": str(Path(__file__).resolve().parent)}
1554
+ r = _sp.run(
1555
+ [sys.executable, str(script)],
1556
+ env=env,
1557
+ capture_output=True,
1558
+ text=True,
1559
+ timeout=30,
1560
+ )
1561
+ if r.returncode == 0:
1562
+ line = (r.stdout.strip().splitlines() or ["ok"])[-1]
1563
+ _log(f"F1 email migration: {line}")
1564
+ else:
1565
+ _log(f"F1 email migration FAILED (rc={r.returncode}): {r.stderr.strip()[:200]}")
1566
+ except _sp.TimeoutExpired:
1567
+ _log("F1 email migration timed out after 30s")
1568
+ except Exception as exc:
1569
+ _log(f"F1 email migration skipped: {exc}")
1570
+
1571
+
1528
1572
  # ── npm version check (notify only) ─────────────────────────────────
1529
1573
 
1530
1574
  def _check_npm_version() -> str | None:
package/src/cli.py CHANGED
@@ -452,6 +452,49 @@ def _scripts_unschedule(args):
452
452
  return 0 if result.get("ok") else 1
453
453
 
454
454
 
455
+ def _scripts_set_enabled(args, enabled):
456
+ from script_registry import set_personal_script_enabled
457
+
458
+ result = set_personal_script_enabled(args.name, enabled)
459
+ if args.json:
460
+ print(json.dumps(result, indent=2, ensure_ascii=False))
461
+ return 0 if result.get("ok") else 1
462
+ if not result.get("ok"):
463
+ print(result.get("error", "Failed to toggle script"), file=sys.stderr)
464
+ return 1
465
+ verb = "enabled" if enabled else "disabled"
466
+ if result.get("changed"):
467
+ print(f"Script {result['name']} {verb}.")
468
+ else:
469
+ print(f"Script {result['name']} already {verb}.")
470
+ return 0
471
+
472
+
473
+ def _scripts_status(args):
474
+ from script_registry import get_personal_script_status
475
+
476
+ result = get_personal_script_status(args.name)
477
+ if args.json:
478
+ print(json.dumps(result, indent=2, ensure_ascii=False))
479
+ return 0 if result.get("ok") else 1
480
+ if not result.get("ok"):
481
+ print(result.get("error", "Failed to read status"), file=sys.stderr)
482
+ return 1
483
+ state = "enabled" if result.get("enabled") else "DISABLED"
484
+ print(f"{result.get('name')} [{result.get('classification')}] -> {state}")
485
+ last = result.get("last_run") or {}
486
+ if last:
487
+ exit_code = last.get("exit_code")
488
+ started = last.get("started_at") or "?"
489
+ print(f" last run: {started} (exit={exit_code})")
490
+ summary = (last.get("summary") or "").strip()
491
+ if summary:
492
+ print(f" summary: {summary[:120]}")
493
+ else:
494
+ print(" last run: (none)")
495
+ return 0
496
+
497
+
455
498
  def _scripts_remove(args):
456
499
  from script_registry import remove_personal_script
457
500
 
@@ -2046,6 +2089,13 @@ def main():
2046
2089
  parser.add_argument("-v", "--version", action="store_true", help="Show version")
2047
2090
  sub = parser.add_subparsers(dest="command")
2048
2091
 
2092
+ # -- email (Plan F1 — interactive wizard for email accounts) --
2093
+ try:
2094
+ from cli_email import register_email_parser
2095
+ register_email_parser(sub)
2096
+ except Exception as _exc_email: # pragma: no cover
2097
+ pass
2098
+
2049
2099
  # -- chat --
2050
2100
  chat_parser = sub.add_parser("chat", help="Launch a NEXO terminal client")
2051
2101
  chat_parser.add_argument("path", nargs="?", default=".", help="Working directory (default: current directory)")
@@ -2109,6 +2159,19 @@ def main():
2109
2159
  unschedule_p.add_argument("name", help="Script name or path")
2110
2160
  unschedule_p.add_argument("--json", action="store_true", help="JSON output")
2111
2161
 
2162
+ # scripts enable / disable / status (Plan F0.2.2)
2163
+ enable_p = scripts_sub.add_parser("enable", help="Enable a personal script (cron wrapper will run it again)")
2164
+ enable_p.add_argument("name", help="Script name or path")
2165
+ enable_p.add_argument("--json", action="store_true", help="JSON output")
2166
+
2167
+ disable_p = scripts_sub.add_parser("disable", help="Disable a personal script (cron wrapper will skip it)")
2168
+ disable_p.add_argument("name", help="Script name or path")
2169
+ disable_p.add_argument("--json", action="store_true", help="JSON output")
2170
+
2171
+ status_p = scripts_sub.add_parser("status", help="Show enabled flag + last cron_runs row for a script")
2172
+ status_p.add_argument("name", help="Script name or path")
2173
+ status_p.add_argument("--json", action="store_true", help="JSON output")
2174
+
2112
2175
  # scripts remove
2113
2176
  remove_p = scripts_sub.add_parser("remove", help="Remove a personal script and any attached schedules")
2114
2177
  remove_p.add_argument("name", help="Script name or path")
@@ -2354,6 +2417,14 @@ def main():
2354
2417
  print(f"nexo v{_get_version()}")
2355
2418
  return 0
2356
2419
 
2420
+ if args.command == "email":
2421
+ # Plan F1 — setup / list / test / remove cuentas email.
2422
+ fn = getattr(args, "func", None)
2423
+ if fn is None:
2424
+ print("usage: nexo email {setup,list,test,remove}")
2425
+ return 1
2426
+ return int(fn(args) or 0)
2427
+
2357
2428
  if args.command == "scripts":
2358
2429
  if args.scripts_command == "list":
2359
2430
  return _scripts_list(args)
@@ -2379,6 +2450,12 @@ def main():
2379
2450
  return _scripts_doctor(args)
2380
2451
  elif args.scripts_command == "call":
2381
2452
  return _scripts_call(args)
2453
+ elif args.scripts_command == "enable":
2454
+ return _scripts_set_enabled(args, True)
2455
+ elif args.scripts_command == "disable":
2456
+ return _scripts_set_enabled(args, False)
2457
+ elif args.scripts_command == "status":
2458
+ return _scripts_status(args)
2382
2459
  else:
2383
2460
  scripts_parser.print_help()
2384
2461
  return 0