nexo-brain 5.3.1 → 5.3.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -0
- package/bin/nexo-brain.js +29 -0
- package/hooks/hooks.json +17 -0
- package/package.json +1 -1
- package/src/auto_update.py +4 -0
- package/src/client_sync.py +12 -0
- package/src/hooks/heartbeat-enforcement.py +90 -0
- package/src/hooks/heartbeat-posttool.sh +18 -0
- package/src/hooks/heartbeat-user-msg.sh +15 -0
- package/src/plugins/update.py +44 -1
- package/src/script_registry.py +29 -1
- package/templates/launchagents/README.md +0 -1
- package/templates/launchagents/com.nexo.github-monitor.plist +0 -45
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.2",
|
|
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
|
@@ -87,6 +87,8 @@ Versions `3.1.7` through `3.2.0` close the recent-memory gap:
|
|
|
87
87
|
- when even that misses, NEXO now exposes raw transcript fallback tools for Claude Code and Codex session stores
|
|
88
88
|
- NEXO can now inspect itself through a live system catalog derived from canonical sources instead of relying only on stale docs or operator memory
|
|
89
89
|
|
|
90
|
+
Version `5.3.2` hardens the packaged runtime boundary: NEXO now persists which runtime scripts/hooks are core product artifacts, `nexo scripts` no longer mixes those into the personal bucket, and `nexo update` migrates the legacy Claude Code heartbeat wrappers into managed core hooks.
|
|
91
|
+
|
|
90
92
|
Version `5.3.1` normalizes packaged npm installs so they behave like packaged npm installs: `nexo update` now keeps the runtime anchored to `~/.nexo`, refreshes packaged bootstrap/client artifacts after upgrade, avoids repo-only release-artifact drift in installed runtimes, and keeps personal scripts on the canonical packaged path.
|
|
91
93
|
|
|
92
94
|
Version `5.3.0` adds `nexo uninstall` — a CLI command that cleanly separates runtime from user data. It stops all crons, removes the MCP server config, and preserves databases, learnings, and personal scripts for safe reinstall.
|
package/bin/nexo-brain.js
CHANGED
|
@@ -93,6 +93,33 @@ function syncWatchdogHashRegistry(nexoHome) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function writeRuntimeCoreArtifactsManifest(nexoHome, srcDir) {
|
|
97
|
+
try {
|
|
98
|
+
const listTopLevelFiles = (dirPath) => {
|
|
99
|
+
if (!fs.existsSync(dirPath)) return [];
|
|
100
|
+
return fs.readdirSync(dirPath)
|
|
101
|
+
.filter((name) => {
|
|
102
|
+
const full = path.join(dirPath, name);
|
|
103
|
+
return fs.existsSync(full) && fs.statSync(full).isFile();
|
|
104
|
+
})
|
|
105
|
+
.sort();
|
|
106
|
+
};
|
|
107
|
+
const configDir = path.join(nexoHome, "config");
|
|
108
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
109
|
+
const payload = {
|
|
110
|
+
generated_at: new Date().toISOString(),
|
|
111
|
+
script_names: listTopLevelFiles(path.join(srcDir, "scripts")),
|
|
112
|
+
hook_names: listTopLevelFiles(path.join(srcDir, "hooks")),
|
|
113
|
+
};
|
|
114
|
+
fs.writeFileSync(
|
|
115
|
+
path.join(configDir, "runtime-core-artifacts.json"),
|
|
116
|
+
`${JSON.stringify(payload, null, 2)}\n`
|
|
117
|
+
);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
log(`WARN: could not write runtime core-artifacts manifest: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
96
123
|
function getCoreRuntimeFlatFiles() {
|
|
97
124
|
return [
|
|
98
125
|
"server.py",
|
|
@@ -1538,6 +1565,7 @@ async function main() {
|
|
|
1538
1565
|
fs.chmodSync(path.join(scriptsDest, f), "755");
|
|
1539
1566
|
});
|
|
1540
1567
|
}
|
|
1568
|
+
writeRuntimeCoreArtifactsManifest(NEXO_HOME, srcDir);
|
|
1541
1569
|
log(" Scripts updated.");
|
|
1542
1570
|
|
|
1543
1571
|
// Register ALL 8 core hooks in settings.json (additive — don't remove user's custom hooks)
|
|
@@ -2359,6 +2387,7 @@ async function main() {
|
|
|
2359
2387
|
});
|
|
2360
2388
|
log(" Hooks installed.");
|
|
2361
2389
|
}
|
|
2390
|
+
writeRuntimeCoreArtifactsManifest(NEXO_HOME, srcDir);
|
|
2362
2391
|
|
|
2363
2392
|
// Generate personality
|
|
2364
2393
|
const personality = `# ${operatorName} — Personality
|
package/hooks/hooks.json
CHANGED
|
@@ -50,6 +50,18 @@
|
|
|
50
50
|
]
|
|
51
51
|
}
|
|
52
52
|
],
|
|
53
|
+
"UserPromptSubmit": [
|
|
54
|
+
{
|
|
55
|
+
"matcher": "*",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "NEXO_HOME=\"${CLAUDE_PLUGIN_DATA}\" NEXO_CODE=\"${CLAUDE_PLUGIN_ROOT}/src\" bash \"${CLAUDE_PLUGIN_ROOT}/src/hooks/heartbeat-user-msg.sh\"",
|
|
60
|
+
"timeout": 3
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
53
65
|
"PostToolUse": [
|
|
54
66
|
{
|
|
55
67
|
"matcher": "*",
|
|
@@ -73,6 +85,11 @@
|
|
|
73
85
|
"type": "command",
|
|
74
86
|
"command": "NEXO_HOME=\"${CLAUDE_PLUGIN_DATA}\" NEXO_CODE=\"${CLAUDE_PLUGIN_ROOT}/src\" bash \"${CLAUDE_PLUGIN_ROOT}/src/hooks/protocol-guardrail.sh\"",
|
|
75
87
|
"timeout": 5
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"type": "command",
|
|
91
|
+
"command": "NEXO_HOME=\"${CLAUDE_PLUGIN_DATA}\" NEXO_CODE=\"${CLAUDE_PLUGIN_ROOT}/src\" bash \"${CLAUDE_PLUGIN_ROOT}/src/hooks/heartbeat-posttool.sh\"",
|
|
92
|
+
"timeout": 3
|
|
76
93
|
}
|
|
77
94
|
]
|
|
78
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.2",
|
|
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",
|
package/src/auto_update.py
CHANGED
|
@@ -359,6 +359,10 @@ def _cleanup_retired_runtime_files():
|
|
|
359
359
|
"""Remove retired core files that should not survive updates."""
|
|
360
360
|
retired = [
|
|
361
361
|
NEXO_HOME / "scripts" / "nexo-day-orchestrator.sh",
|
|
362
|
+
NEXO_HOME / "scripts" / "heartbeat-enforcement.py",
|
|
363
|
+
NEXO_HOME / "scripts" / "heartbeat-posttool.sh",
|
|
364
|
+
NEXO_HOME / "scripts" / "heartbeat-user-msg.sh",
|
|
365
|
+
NEXO_HOME / "hooks" / "heartbeat-guard.sh",
|
|
362
366
|
]
|
|
363
367
|
for target in retired:
|
|
364
368
|
try:
|
package/src/client_sync.py
CHANGED
|
@@ -349,6 +349,12 @@ CORE_HOOK_SPECS = [
|
|
|
349
349
|
"timeout": 5,
|
|
350
350
|
"script": "protocol-pretool-guardrail.sh",
|
|
351
351
|
},
|
|
352
|
+
{
|
|
353
|
+
"event": "UserPromptSubmit",
|
|
354
|
+
"identity": "heartbeat-user-msg.sh",
|
|
355
|
+
"timeout": 3,
|
|
356
|
+
"script": "heartbeat-user-msg.sh",
|
|
357
|
+
},
|
|
352
358
|
{
|
|
353
359
|
"event": "PostToolUse",
|
|
354
360
|
"identity": "capture-tool-logs.sh",
|
|
@@ -373,6 +379,12 @@ CORE_HOOK_SPECS = [
|
|
|
373
379
|
"timeout": 5,
|
|
374
380
|
"script": "protocol-guardrail.sh",
|
|
375
381
|
},
|
|
382
|
+
{
|
|
383
|
+
"event": "PostToolUse",
|
|
384
|
+
"identity": "heartbeat-posttool.sh",
|
|
385
|
+
"timeout": 3,
|
|
386
|
+
"script": "heartbeat-posttool.sh",
|
|
387
|
+
},
|
|
376
388
|
{
|
|
377
389
|
"event": "PreCompact",
|
|
378
390
|
"identity": "pre-compact.sh",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Heartbeat enforcement for NEXO sessions.
|
|
3
|
+
|
|
4
|
+
Tracks user messages vs heartbeat calls. Emits a warning when more than two
|
|
5
|
+
user messages pass without a heartbeat call.
|
|
6
|
+
|
|
7
|
+
Modes:
|
|
8
|
+
- HEARTBEAT_MODE=user_msg: increment counter on UserPromptSubmit
|
|
9
|
+
- HEARTBEAT_MODE=post_tool: inspect PostToolUse payload, reset on heartbeat,
|
|
10
|
+
warn when other tools keep running without one
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
STATE_FILE = Path(os.environ.get("NEXO_HOME", Path.home() / ".nexo")) / "operations" / ".heartbeat-state.json"
|
|
22
|
+
THRESHOLD = 2
|
|
23
|
+
HEARTBEAT_TOOL = "nexo_heartbeat"
|
|
24
|
+
SKIP_TOOLS = {"nexo_startup", "nexo_stop", "nexo_smart_startup"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _read_state() -> dict:
|
|
28
|
+
try:
|
|
29
|
+
return json.loads(STATE_FILE.read_text())
|
|
30
|
+
except Exception:
|
|
31
|
+
return {"user_msgs": 0, "last_heartbeat_ts": 0.0, "last_user_msg_ts": 0.0}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _write_state(state: dict) -> None:
|
|
35
|
+
try:
|
|
36
|
+
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
STATE_FILE.write_text(json.dumps(state))
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def handle_user_message() -> int:
|
|
43
|
+
state = _read_state()
|
|
44
|
+
state["user_msgs"] = state.get("user_msgs", 0) + 1
|
|
45
|
+
state["last_user_msg_ts"] = time.time()
|
|
46
|
+
_write_state(state)
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def handle_post_tool(payload: dict) -> int:
|
|
51
|
+
tool_name = str(payload.get("tool_name", "")).strip()
|
|
52
|
+
short_name = tool_name.rsplit("__", 1)[-1] if "__" in tool_name else tool_name
|
|
53
|
+
state = _read_state()
|
|
54
|
+
|
|
55
|
+
if short_name == HEARTBEAT_TOOL:
|
|
56
|
+
state["user_msgs"] = 0
|
|
57
|
+
state["last_heartbeat_ts"] = time.time()
|
|
58
|
+
_write_state(state)
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
if short_name in SKIP_TOOLS:
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
user_msgs = state.get("user_msgs", 0)
|
|
65
|
+
if user_msgs > THRESHOLD:
|
|
66
|
+
print(
|
|
67
|
+
f"\nWARNING: HEARTBEAT OVERDUE ({user_msgs} user messages without nexo_heartbeat). "
|
|
68
|
+
"Call nexo_heartbeat(sid=SID, task='...') before continuing."
|
|
69
|
+
)
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main() -> int:
|
|
74
|
+
mode = os.environ.get("HEARTBEAT_MODE", "").strip()
|
|
75
|
+
if mode == "user_msg":
|
|
76
|
+
return handle_user_message()
|
|
77
|
+
if mode == "post_tool":
|
|
78
|
+
raw = sys.stdin.read()
|
|
79
|
+
if not raw.strip():
|
|
80
|
+
return 0
|
|
81
|
+
try:
|
|
82
|
+
payload = json.loads(raw)
|
|
83
|
+
except Exception:
|
|
84
|
+
return 0
|
|
85
|
+
return handle_post_tool(payload)
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# NEXO PostToolUse hook — heartbeat enforcement checker
|
|
3
|
+
set -uo pipefail
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat || true)
|
|
6
|
+
[ -z "$INPUT" ] && exit 0
|
|
7
|
+
|
|
8
|
+
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
9
|
+
HELPER=""
|
|
10
|
+
if [ -n "${NEXO_CODE:-}" ] && [ -f "${NEXO_CODE%/}/hooks/heartbeat-enforcement.py" ]; then
|
|
11
|
+
HELPER="${NEXO_CODE%/}/hooks/heartbeat-enforcement.py"
|
|
12
|
+
elif [ -f "$NEXO_HOME/hooks/heartbeat-enforcement.py" ]; then
|
|
13
|
+
HELPER="$NEXO_HOME/hooks/heartbeat-enforcement.py"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
[ -z "$HELPER" ] && exit 0
|
|
17
|
+
HEARTBEAT_MODE=post_tool python3 "$HELPER" <<< "$INPUT" 2>/dev/null || true
|
|
18
|
+
exit 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# NEXO UserPromptSubmit hook — track user messages for heartbeat enforcement
|
|
3
|
+
set -uo pipefail
|
|
4
|
+
|
|
5
|
+
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
6
|
+
HELPER=""
|
|
7
|
+
if [ -n "${NEXO_CODE:-}" ] && [ -f "${NEXO_CODE%/}/hooks/heartbeat-enforcement.py" ]; then
|
|
8
|
+
HELPER="${NEXO_CODE%/}/hooks/heartbeat-enforcement.py"
|
|
9
|
+
elif [ -f "$NEXO_HOME/hooks/heartbeat-enforcement.py" ]; then
|
|
10
|
+
HELPER="$NEXO_HOME/hooks/heartbeat-enforcement.py"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
[ -z "$HELPER" ] && exit 0
|
|
14
|
+
HEARTBEAT_MODE=user_msg python3 "$HELPER" 2>/dev/null || true
|
|
15
|
+
exit 0
|
package/src/plugins/update.py
CHANGED
|
@@ -81,7 +81,7 @@ def _is_git_repo() -> bool:
|
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def _refresh_installed_manifest():
|
|
84
|
-
"""
|
|
84
|
+
"""Refresh packaged crons and persist the runtime core-artifacts manifest."""
|
|
85
85
|
try:
|
|
86
86
|
src_crons = SRC_DIR / "crons"
|
|
87
87
|
dst_crons = NEXO_HOME / "crons"
|
|
@@ -90,10 +90,45 @@ def _refresh_installed_manifest():
|
|
|
90
90
|
for f in src_crons.iterdir():
|
|
91
91
|
if f.is_file():
|
|
92
92
|
shutil.copy2(str(f), str(dst_crons / f.name))
|
|
93
|
+
config_dir = NEXO_HOME / "config"
|
|
94
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
payload = {
|
|
96
|
+
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
97
|
+
"script_names": sorted(
|
|
98
|
+
f.name for f in (SRC_DIR / "scripts").iterdir()
|
|
99
|
+
if f.is_file()
|
|
100
|
+
) if (SRC_DIR / "scripts").is_dir() else [],
|
|
101
|
+
"hook_names": sorted(
|
|
102
|
+
f.name for f in (SRC_DIR / "hooks").iterdir()
|
|
103
|
+
if f.is_file()
|
|
104
|
+
) if (SRC_DIR / "hooks").is_dir() else [],
|
|
105
|
+
}
|
|
106
|
+
(config_dir / "runtime-core-artifacts.json").write_text(
|
|
107
|
+
json.dumps(payload, indent=2, ensure_ascii=False) + "\n"
|
|
108
|
+
)
|
|
93
109
|
except Exception:
|
|
94
110
|
pass
|
|
95
111
|
|
|
96
112
|
|
|
113
|
+
def _cleanup_retired_runtime_files() -> list[str]:
|
|
114
|
+
removed: list[str] = []
|
|
115
|
+
retired_paths = [
|
|
116
|
+
NEXO_HOME / "scripts" / "heartbeat-enforcement.py",
|
|
117
|
+
NEXO_HOME / "scripts" / "heartbeat-posttool.sh",
|
|
118
|
+
NEXO_HOME / "scripts" / "heartbeat-user-msg.sh",
|
|
119
|
+
NEXO_HOME / "hooks" / "heartbeat-guard.sh",
|
|
120
|
+
]
|
|
121
|
+
for path in retired_paths:
|
|
122
|
+
if not path.exists():
|
|
123
|
+
continue
|
|
124
|
+
try:
|
|
125
|
+
path.unlink()
|
|
126
|
+
removed.append(str(path))
|
|
127
|
+
except Exception:
|
|
128
|
+
continue
|
|
129
|
+
return removed
|
|
130
|
+
|
|
131
|
+
|
|
97
132
|
def _read_version() -> str:
|
|
98
133
|
"""Read the installed/runtime version."""
|
|
99
134
|
if _PACKAGED_INSTALL:
|
|
@@ -546,10 +581,12 @@ def _handle_packaged_update(progress_fn=None) -> str:
|
|
|
546
581
|
errors.append(f"verification: {verify_err}")
|
|
547
582
|
|
|
548
583
|
hook_sync_warning = None
|
|
584
|
+
retired_runtime_files: list[str] = []
|
|
549
585
|
try:
|
|
550
586
|
_emit_progress(progress_fn, "Refreshing installed hooks and manifests...")
|
|
551
587
|
_refresh_installed_manifest()
|
|
552
588
|
_sync_hooks_to_home()
|
|
589
|
+
retired_runtime_files = _cleanup_retired_runtime_files()
|
|
553
590
|
except Exception as e:
|
|
554
591
|
hook_sync_warning = f"{e}"
|
|
555
592
|
|
|
@@ -599,6 +636,8 @@ def _handle_packaged_update(progress_fn=None) -> str:
|
|
|
599
636
|
lines.append(" Hooks: synced to NEXO_HOME")
|
|
600
637
|
else:
|
|
601
638
|
lines.append(f" WARNING: hook sync: {hook_sync_warning}")
|
|
639
|
+
if retired_runtime_files:
|
|
640
|
+
lines.append(f" Cleanup: removed {len(retired_runtime_files)} retired runtime file(s)")
|
|
602
641
|
if not client_sync_warning:
|
|
603
642
|
lines.append(" Clients: configured client targets synced")
|
|
604
643
|
else:
|
|
@@ -714,9 +753,11 @@ def handle_update(remote: str = "origin", branch: str = "main", progress_fn=None
|
|
|
714
753
|
cron_sync_result = f"Cron sync warning: {e}"
|
|
715
754
|
|
|
716
755
|
# Step 9: Sync hooks to NEXO_HOME
|
|
756
|
+
retired_runtime_files: list[str] = []
|
|
717
757
|
try:
|
|
718
758
|
_emit_progress(progress_fn, "Syncing core Claude hooks...")
|
|
719
759
|
_sync_hooks_to_home()
|
|
760
|
+
retired_runtime_files = _cleanup_retired_runtime_files()
|
|
720
761
|
steps_done.append("hook-sync")
|
|
721
762
|
except Exception as e:
|
|
722
763
|
pass # Non-critical, log in function
|
|
@@ -768,6 +809,8 @@ def handle_update(remote: str = "origin", branch: str = "main", progress_fn=None
|
|
|
768
809
|
lines.append(" Crons: synced with manifest")
|
|
769
810
|
if "hook-sync" in steps_done:
|
|
770
811
|
lines.append(" Hooks: synced to NEXO_HOME")
|
|
812
|
+
if retired_runtime_files:
|
|
813
|
+
lines.append(f" Cleanup: removed {len(retired_runtime_files)} retired runtime file(s)")
|
|
771
814
|
if "client-sync" in steps_done:
|
|
772
815
|
lines.append(" Clients: configured client targets synced")
|
|
773
816
|
lines.append("")
|
package/src/script_registry.py
CHANGED
|
@@ -44,6 +44,14 @@ _LEGACY_WAKE_RECOVERY_METADATA = [
|
|
|
44
44
|
"# nexo: run_on_boot=true",
|
|
45
45
|
]
|
|
46
46
|
|
|
47
|
+
_LEGACY_CORE_RUNTIME_FILES = {
|
|
48
|
+
"capture-tool-logs.sh",
|
|
49
|
+
"daily-briefing-check.sh",
|
|
50
|
+
"heartbeat-enforcement.py",
|
|
51
|
+
"heartbeat-posttool.sh",
|
|
52
|
+
"heartbeat-user-msg.sh",
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
# Forbidden patterns — direct DB access from personal scripts
|
|
48
56
|
_FORBIDDEN_PATTERNS = [
|
|
49
57
|
re.compile(r"\bsqlite3\b"),
|
|
@@ -119,7 +127,7 @@ def _apply_legacy_personal_script_backfills() -> None:
|
|
|
119
127
|
|
|
120
128
|
|
|
121
129
|
def load_core_script_names() -> set[str]:
|
|
122
|
-
"""Load script names
|
|
130
|
+
"""Load runtime-managed script names (core, not personal)."""
|
|
123
131
|
names: set[str] = set()
|
|
124
132
|
for manifest_path in [NEXO_CODE / "crons" / "manifest.json", NEXO_HOME / "crons" / "manifest.json"]:
|
|
125
133
|
if manifest_path.exists():
|
|
@@ -132,6 +140,26 @@ def load_core_script_names() -> set[str]:
|
|
|
132
140
|
break
|
|
133
141
|
except Exception:
|
|
134
142
|
continue
|
|
143
|
+
runtime_manifest = NEXO_HOME / "config" / "runtime-core-artifacts.json"
|
|
144
|
+
if runtime_manifest.exists():
|
|
145
|
+
try:
|
|
146
|
+
data = json.loads(runtime_manifest.read_text())
|
|
147
|
+
for key in ("script_names", "hook_names"):
|
|
148
|
+
for name in data.get(key, []):
|
|
149
|
+
clean = Path(str(name)).name
|
|
150
|
+
if clean:
|
|
151
|
+
names.add(clean)
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
hooks_dir = NEXO_HOME / "hooks"
|
|
155
|
+
if hooks_dir.is_dir():
|
|
156
|
+
try:
|
|
157
|
+
for item in hooks_dir.iterdir():
|
|
158
|
+
if item.is_file():
|
|
159
|
+
names.add(item.name)
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
names.update(_LEGACY_CORE_RUNTIME_FILES)
|
|
135
163
|
return names
|
|
136
164
|
|
|
137
165
|
|
|
@@ -112,7 +112,6 @@ These agents power NEXO's learning and memory systems. Strongly recommended.
|
|
|
112
112
|
| File | Schedule | What it does |
|
|
113
113
|
|------|----------|-------------|
|
|
114
114
|
| `com.nexo.dashboard.plist` | Persistent (KeepAlive) | Runs the NEXO web dashboard on `http://localhost:6174`. Provides a browser-based view of sessions, reminders, followups, and system health. Only needed if you want the dashboard UI. |
|
|
115
|
-
| `com.nexo.github-monitor.plist` | Daily 08:00 | Checks the NEXO public GitHub repository for open issues, pull requests, and pending releases. Writes results to `~/.nexo/github-status.json` for NEXO to read at startup. Only relevant if you maintain the public NEXO repository. |
|
|
116
115
|
|
|
117
116
|
---
|
|
118
117
|
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<!-- com.nexo.github-monitor
|
|
3
|
-
Runs nexo-github-monitor.py every day at 08:00 to check the NEXO
|
|
4
|
-
public GitHub repository for new issues, pull requests, and pending
|
|
5
|
-
releases. Writes results to ~/.nexo/github-status.json. At the next
|
|
6
|
-
session startup, NEXO reads this file and responds to open issues,
|
|
7
|
-
reviews PRs, and proposes a release if enough commits have
|
|
8
|
-
accumulated since the last tag.
|
|
9
|
-
-->
|
|
10
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
11
|
-
<plist version="1.0">
|
|
12
|
-
<dict>
|
|
13
|
-
<key>Label</key>
|
|
14
|
-
<string>com.nexo.github-monitor</string>
|
|
15
|
-
<key>ProgramArguments</key>
|
|
16
|
-
<array>
|
|
17
|
-
<string>/usr/bin/python3</string>
|
|
18
|
-
<string>{{NEXO_HOME}}/scripts/nexo-github-monitor.py</string>
|
|
19
|
-
</array>
|
|
20
|
-
<key>StartCalendarInterval</key>
|
|
21
|
-
<dict>
|
|
22
|
-
<key>Hour</key>
|
|
23
|
-
<integer>8</integer>
|
|
24
|
-
<key>Minute</key>
|
|
25
|
-
<integer>0</integer>
|
|
26
|
-
</dict>
|
|
27
|
-
<key>StandardOutPath</key>
|
|
28
|
-
<string>{{NEXO_HOME}}/logs/github-monitor-stdout.log</string>
|
|
29
|
-
<key>StandardErrorPath</key>
|
|
30
|
-
<string>{{NEXO_HOME}}/logs/github-monitor-stderr.log</string>
|
|
31
|
-
<key>EnvironmentVariables</key>
|
|
32
|
-
<dict>
|
|
33
|
-
<key>HOME</key>
|
|
34
|
-
<string>{{HOME}}</string>
|
|
35
|
-
<key>NEXO_HOME</key>
|
|
36
|
-
<string>{{NEXO_HOME}}</string>
|
|
37
|
-
<key>NEXO_CODE</key>
|
|
38
|
-
<string>{{NEXO_CODE}}</string>
|
|
39
|
-
<key>PATH</key>
|
|
40
|
-
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
41
|
-
</dict>
|
|
42
|
-
<key>RunAtLoad</key>
|
|
43
|
-
<false/>
|
|
44
|
-
</dict>
|
|
45
|
-
</plist>
|