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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +10 -1
- package/package.json +1 -1
- package/src/auto_update.py +44 -0
- package/src/cli.py +77 -0
- package/src/cli_email.py +492 -0
- package/src/db/_email_accounts.py +170 -0
- package/src/db/_personal_scripts.py +3 -1
- package/src/db/_schema.py +50 -0
- package/src/email_config.py +143 -0
- package/src/presets/entities_local.sample.json +30 -0
- package/src/presets/entities_universal.json +4 -81
- package/src/r23b_deploy_vhost.py +2 -2
- package/src/script_registry.py +104 -0
- package/src/scripts/nexo-cron-wrapper.sh +38 -0
- package/src/scripts/nexo-email-migrate-config.py +145 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "6.
|
|
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.
|
|
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
|
+
"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",
|
package/src/auto_update.py
CHANGED
|
@@ -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
|