nexo-brain 5.3.18 → 5.3.20
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/bin/nexo-brain.js +161 -17
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.20",
|
|
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/bin/nexo-brain.js
CHANGED
|
@@ -1692,6 +1692,40 @@ async function main() {
|
|
|
1692
1692
|
}
|
|
1693
1693
|
}
|
|
1694
1694
|
|
|
1695
|
+
// Restore operator shell alias + PATH if lost during previous updates
|
|
1696
|
+
const migOperatorName = installed.operator_name || "NEXO";
|
|
1697
|
+
const migAliasName = migOperatorName.toLowerCase();
|
|
1698
|
+
if (migAliasName !== "nexo") {
|
|
1699
|
+
const migAliasLine = `alias ${migAliasName}='nexo chat .'`;
|
|
1700
|
+
const migAliasComment = `# ${migOperatorName} — open the configured NEXO terminal client`;
|
|
1701
|
+
const migNexoPathLine = `export PATH="${path.join(NEXO_HOME, "bin")}:$PATH"`;
|
|
1702
|
+
const migNexoPathComment = "# NEXO runtime CLI";
|
|
1703
|
+
const migUserShell = process.env.SHELL || "/bin/bash";
|
|
1704
|
+
const migHomeDir = require("os").homedir();
|
|
1705
|
+
const migRcFiles = [];
|
|
1706
|
+
if (migUserShell.includes("zsh")) {
|
|
1707
|
+
migRcFiles.push(path.join(migHomeDir, ".zshrc"));
|
|
1708
|
+
} else {
|
|
1709
|
+
migRcFiles.push(path.join(migHomeDir, ".bash_profile"));
|
|
1710
|
+
migRcFiles.push(path.join(migHomeDir, ".bashrc"));
|
|
1711
|
+
}
|
|
1712
|
+
for (const rcFile of migRcFiles) {
|
|
1713
|
+
let rcContent = "";
|
|
1714
|
+
if (fs.existsSync(rcFile)) {
|
|
1715
|
+
rcContent = fs.readFileSync(rcFile, "utf8");
|
|
1716
|
+
}
|
|
1717
|
+
if (!rcContent.includes(migNexoPathLine)) {
|
|
1718
|
+
fs.appendFileSync(rcFile, `\n${migNexoPathComment}\n${migNexoPathLine}\n`);
|
|
1719
|
+
log(` Restored NEXO runtime CLI in ${path.basename(rcFile)}`);
|
|
1720
|
+
rcContent += `\n${migNexoPathComment}\n${migNexoPathLine}\n`;
|
|
1721
|
+
}
|
|
1722
|
+
if (!rcContent.includes(`alias ${migAliasName}=`)) {
|
|
1723
|
+
fs.appendFileSync(rcFile, `\n${migAliasComment}\n${migAliasLine}\n`);
|
|
1724
|
+
log(` Restored '${migAliasName}' alias in ${path.basename(rcFile)}`);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1695
1729
|
console.log("");
|
|
1696
1730
|
log(`Migration complete: v${installedVersion} → v${currentVersion}`);
|
|
1697
1731
|
log("Your data (memories, learnings, preferences) is untouched.");
|
|
@@ -2511,6 +2545,9 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
|
|
|
2511
2545
|
calendar: {},
|
|
2512
2546
|
contacts: [],
|
|
2513
2547
|
documents: {},
|
|
2548
|
+
notes: { count: 0, folders: [] },
|
|
2549
|
+
reminders: { count: 0, lists: [] },
|
|
2550
|
+
photos: { count: 0 },
|
|
2514
2551
|
messaging: [],
|
|
2515
2552
|
interests: [],
|
|
2516
2553
|
summary: {},
|
|
@@ -2747,16 +2784,27 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
|
|
|
2747
2784
|
// --- Email accounts ---
|
|
2748
2785
|
process.stdout.write(" \u280B Email accounts...\r");
|
|
2749
2786
|
if (platform === "darwin") {
|
|
2750
|
-
// macOS Mail.app accounts
|
|
2751
|
-
|
|
2787
|
+
// macOS Mail.app accounts — try multiple detection methods
|
|
2788
|
+
// Method 1: sandboxed container (modern macOS)
|
|
2789
|
+
let mailAccounts = run("defaults read ~/Library/Containers/com.apple.mail/Data/Library/Preferences/com.apple.mail MailAccounts 2>/dev/null | grep -E 'AccountName|EmailAddresses' | head -30");
|
|
2790
|
+
// Method 2: legacy plist (older macOS)
|
|
2791
|
+
if (!mailAccounts) mailAccounts = run("defaults read com.apple.mail MailAccounts 2>/dev/null | grep -E 'AccountName|EmailAddresses' | head -30");
|
|
2792
|
+
// Method 3: Internet Accounts (covers Mail, Calendar, Contacts, etc.)
|
|
2793
|
+
if (!mailAccounts) mailAccounts = run("defaults read com.apple.internetaccounts Accounts 2>/dev/null | grep -E 'AccountDescription|Username' | head -30");
|
|
2794
|
+
// Method 4: scan Mail directory for account folders
|
|
2795
|
+
if (!mailAccounts) {
|
|
2796
|
+
const mailDir = run("ls -1 ~/Library/Mail/V*/ 2>/dev/null | grep -v '^$' | head -20");
|
|
2797
|
+
if (mailDir) mailAccounts = mailDir;
|
|
2798
|
+
}
|
|
2752
2799
|
if (mailAccounts) {
|
|
2753
2800
|
profileData.email = mailAccounts.split("\n")
|
|
2754
|
-
.map(l => l.replace(/.*=\s*"?/, "").replace(/"?\s*;?\s*$/, "").trim())
|
|
2755
|
-
.filter(
|
|
2801
|
+
.map(l => l.replace(/.*=\s*"?/, "").replace(/"?\s*;?\s*$/, "").replace(/\/$/, "").trim())
|
|
2802
|
+
.filter(l => l && l.length > 1 && !l.startsWith("(") && !l.startsWith(")") && !l.includes("{") && !l.includes("}"))
|
|
2803
|
+
.filter((v, i, a) => a.indexOf(v) === i); // dedupe
|
|
2756
2804
|
}
|
|
2757
2805
|
}
|
|
2758
2806
|
if (profileData.email.length > 0) {
|
|
2759
|
-
log(`\u2713 ${profileData.email.length} email accounts
|
|
2807
|
+
log(`\u2713 ${profileData.email.length} email accounts detected`);
|
|
2760
2808
|
}
|
|
2761
2809
|
|
|
2762
2810
|
// --- Calendar (macOS) ---
|
|
@@ -2815,6 +2863,76 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
|
|
|
2815
2863
|
log(`\u2713 ${totalDocs} recent documents (${typesSummary})`);
|
|
2816
2864
|
}
|
|
2817
2865
|
|
|
2866
|
+
// --- Notes ---
|
|
2867
|
+
process.stdout.write(" \u280B Notes...\r");
|
|
2868
|
+
profileData.notes = { count: 0, folders: [] };
|
|
2869
|
+
if (platform === "linux") {
|
|
2870
|
+
// GNOME Notes / Tomboy / Obsidian vaults
|
|
2871
|
+
const obsidianVaults = run(`find "${home}" -maxdepth 3 -name ".obsidian" -type d 2>/dev/null | head -5`);
|
|
2872
|
+
if (obsidianVaults) {
|
|
2873
|
+
const vaults = obsidianVaults.split("\n").filter(Boolean);
|
|
2874
|
+
let totalNotes = 0;
|
|
2875
|
+
for (const v of vaults) {
|
|
2876
|
+
const vaultDir = path.dirname(v);
|
|
2877
|
+
const count = run(`find "${vaultDir}" -name "*.md" -type f 2>/dev/null | wc -l`);
|
|
2878
|
+
totalNotes += parseInt((count || "0").trim());
|
|
2879
|
+
}
|
|
2880
|
+
profileData.notes.count = totalNotes;
|
|
2881
|
+
profileData.notes.folders = vaults.map(v => path.basename(path.dirname(v)));
|
|
2882
|
+
}
|
|
2883
|
+
} else if (platform === "darwin") {
|
|
2884
|
+
const notesDb = path.join(home, "Library", "Group Containers", "group.com.apple.notes", "NoteStore.sqlite");
|
|
2885
|
+
if (fs.existsSync(notesDb)) {
|
|
2886
|
+
const noteCount = run(`sqlite3 "${notesDb}" "SELECT COUNT(*) FROM ZICCLOUDSYNCINGOBJECT WHERE ZTITLE IS NOT NULL AND ZMARKEDFORDELETION != 1" 2>/dev/null`);
|
|
2887
|
+
profileData.notes.count = parseInt((noteCount || "0").trim());
|
|
2888
|
+
const folders = run(`sqlite3 "${notesDb}" "SELECT DISTINCT ZTITLE2 FROM ZICCLOUDSYNCINGOBJECT WHERE ZTITLE2 IS NOT NULL AND ZMARKEDFORDELETION != 1 LIMIT 15" 2>/dev/null`);
|
|
2889
|
+
if (folders) profileData.notes.folders = folders.split("\n").filter(Boolean);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
if (profileData.notes.count > 0) {
|
|
2893
|
+
log(`\u2713 ${profileData.notes.count} notes${profileData.notes.folders.length ? ` in ${profileData.notes.folders.length} folders` : ""}`);
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
// --- Reminders ---
|
|
2897
|
+
process.stdout.write(" \u280B Reminders...\r");
|
|
2898
|
+
profileData.reminders = { count: 0, lists: [] };
|
|
2899
|
+
if (platform === "linux") {
|
|
2900
|
+
// GNOME Reminders / Todoist / task files
|
|
2901
|
+
const todoFiles = run(`find "${home}" -maxdepth 2 -name "todo.txt" -o -name "TODO.md" -o -name "tasks.md" 2>/dev/null | head -5`);
|
|
2902
|
+
if (todoFiles) {
|
|
2903
|
+
let count = 0;
|
|
2904
|
+
todoFiles.split("\n").filter(Boolean).forEach(f => {
|
|
2905
|
+
const lines = run(`wc -l < "${f}" 2>/dev/null`);
|
|
2906
|
+
count += parseInt((lines || "0").trim());
|
|
2907
|
+
});
|
|
2908
|
+
profileData.reminders.count = count;
|
|
2909
|
+
profileData.reminders.lists = todoFiles.split("\n").filter(Boolean).map(f => path.basename(f));
|
|
2910
|
+
}
|
|
2911
|
+
} else if (platform === "darwin") {
|
|
2912
|
+
// Reminders uses EventKit, but we can count via the Calendars directory or osascript
|
|
2913
|
+
const reminderCount = run('osascript -e \'tell application "Reminders" to count of (every reminder whose completed is false)\' 2>/dev/null');
|
|
2914
|
+
if (reminderCount) profileData.reminders.count = parseInt(reminderCount.trim()) || 0;
|
|
2915
|
+
const reminderLists = run('osascript -e \'tell application "Reminders" to get name of every list\' 2>/dev/null');
|
|
2916
|
+
if (reminderLists) profileData.reminders.lists = reminderLists.split(", ").filter(Boolean);
|
|
2917
|
+
}
|
|
2918
|
+
if (profileData.reminders.count > 0) {
|
|
2919
|
+
log(`\u2713 ${profileData.reminders.count} active reminders across ${profileData.reminders.lists.length} lists`);
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
// --- Photos library size (macOS) ---
|
|
2923
|
+
process.stdout.write(" \u280B Photos...\r");
|
|
2924
|
+
profileData.photos = { count: 0 };
|
|
2925
|
+
if (platform === "darwin") {
|
|
2926
|
+
const photosDb = path.join(home, "Pictures", "Photos Library.photoslibrary", "database", "Photos.sqlite");
|
|
2927
|
+
if (fs.existsSync(photosDb)) {
|
|
2928
|
+
const photoCount = run(`sqlite3 "${photosDb}" "SELECT COUNT(*) FROM ZASSET WHERE ZTRASHEDSTATE = 0" 2>/dev/null`);
|
|
2929
|
+
if (photoCount) profileData.photos.count = parseInt(photoCount.trim()) || 0;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
if (profileData.photos.count > 0) {
|
|
2933
|
+
log(`\u2713 ${profileData.photos.count.toLocaleString()} photos in library`);
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2818
2936
|
// --- Messaging apps ---
|
|
2819
2937
|
process.stdout.write(" \u280B Messaging...\r");
|
|
2820
2938
|
const msgApps = { "WhatsApp": "WhatsApp.app", "Telegram": "Telegram.app", "Slack": "Slack.app", "Discord": "Discord.app", "Signal": "Signal.app", "Teams": "Microsoft Teams.app", "Zoom": "zoom.us.app" };
|
|
@@ -2839,6 +2957,11 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
|
|
|
2839
2957
|
repos: repos.length,
|
|
2840
2958
|
servers: profileData.ssh.length,
|
|
2841
2959
|
email_accounts: profileData.email.length,
|
|
2960
|
+
notes: profileData.notes.count,
|
|
2961
|
+
reminders: profileData.reminders.count,
|
|
2962
|
+
photos: profileData.photos.count,
|
|
2963
|
+
recent_documents: profileData.documents.recent_count || 0,
|
|
2964
|
+
contacts: profileData.contacts.count || 0,
|
|
2842
2965
|
key_tools: topApps.slice(0, 8),
|
|
2843
2966
|
work_hours: profileData.terminal.peak_hours || [],
|
|
2844
2967
|
peak_days: profileData.terminal.peak_days || [],
|
|
@@ -2855,12 +2978,24 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
|
|
|
2855
2978
|
line(`${t.profileTitle}: ${userName || profileData.git.name || "User"}`);
|
|
2856
2979
|
line("");
|
|
2857
2980
|
if (topLangs.length) line(`Stack: ${topLangs.join(", ")}`);
|
|
2858
|
-
|
|
2981
|
+
// Life data
|
|
2982
|
+
const lifeParts = [];
|
|
2983
|
+
if (profileData.email.length) lifeParts.push(`${profileData.email.length} email`);
|
|
2984
|
+
if (profileData.notes.count) lifeParts.push(`${profileData.notes.count} notes`);
|
|
2985
|
+
if (profileData.reminders.count) lifeParts.push(`${profileData.reminders.count} reminders`);
|
|
2986
|
+
if (profileData.contacts.count) lifeParts.push(`${profileData.contacts.count} contacts`);
|
|
2987
|
+
if (profileData.photos.count) lifeParts.push(`${profileData.photos.count.toLocaleString()} photos`);
|
|
2988
|
+
if (profileData.documents.recent_count) lifeParts.push(`${profileData.documents.recent_count} docs`);
|
|
2989
|
+
if (lifeParts.length) line(lifeParts.join(" \u00B7 "));
|
|
2990
|
+
// Dev data
|
|
2991
|
+
if (repos.length || profileData.ssh.length) {
|
|
2992
|
+
line(`${repos.length} repos \u00B7 ${profileData.ssh.length} servers`);
|
|
2993
|
+
}
|
|
2994
|
+
if (topLangs.length) line(`Stack: ${topLangs.join(", ")}`);
|
|
2859
2995
|
if (topApps.length) line(`Tools: ${topApps.slice(0, 6).join(", ")}`);
|
|
2860
2996
|
if (totalCommits) line(`${totalCommits.toLocaleString()} commits/year`);
|
|
2861
2997
|
if (profileData.terminal.peak_hours) {
|
|
2862
2998
|
const hours = profileData.terminal.peak_hours;
|
|
2863
|
-
// Group consecutive hours into ranges
|
|
2864
2999
|
const ranges = [];
|
|
2865
3000
|
let start = hours[0], prev = hours[0];
|
|
2866
3001
|
for (let i = 1; i <= hours.length; i++) {
|
|
@@ -2871,6 +3006,7 @@ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operation
|
|
|
2871
3006
|
line(`Hours: ${ranges.join(", ")}${profileData.terminal.peak_days ? ` \u00B7 Peak: ${profileData.terminal.peak_days.join(", ")}` : ""}`);
|
|
2872
3007
|
}
|
|
2873
3008
|
if (profileData.messaging.length) line(`Messaging: ${profileData.messaging.join(", ")}`);
|
|
3009
|
+
if (profileData.calendar.events) line(`Calendar: ${profileData.calendar.events} events`);
|
|
2874
3010
|
line(`Timezone: ${profileData.system.timezone}`);
|
|
2875
3011
|
line("");
|
|
2876
3012
|
line(lang === "es" ? "Ya te conozco. Vamos a trabajar." : lang === "fr" ? "Je te connais. Au travail." : lang === "de" ? "Ich kenne dich. Los geht's." : lang === "it" ? "Ti conosco. Al lavoro." : lang === "pt" ? "J\u00E1 te conhe\u00E7o. Ao trabalho." : "I know you now. Let's work.");
|
|
@@ -3036,14 +3172,11 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3036
3172
|
await setupKeychainPassFile(NEXO_HOME);
|
|
3037
3173
|
|
|
3038
3174
|
// Step 8: Create shell alias and add runtime CLI to PATH
|
|
3039
|
-
log("Creating shell alias...");
|
|
3040
3175
|
const aliasName = operatorName.toLowerCase();
|
|
3041
|
-
const aliasLine = `alias ${aliasName}='nexo chat .'`;
|
|
3042
|
-
const aliasComment = `# ${operatorName} — open the configured NEXO terminal client`;
|
|
3043
3176
|
const nexoPathLine = `export PATH="${path.join(NEXO_HOME, "bin")}:$PATH"`;
|
|
3044
3177
|
const nexoPathComment = "# NEXO runtime CLI";
|
|
3045
3178
|
|
|
3046
|
-
// Detect shell
|
|
3179
|
+
// Detect shell rc files
|
|
3047
3180
|
const userShell = process.env.SHELL || "/bin/bash";
|
|
3048
3181
|
const homeDir = require("os").homedir();
|
|
3049
3182
|
const rcFiles = [];
|
|
@@ -3058,6 +3191,11 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3058
3191
|
rcFiles.push(bashrc);
|
|
3059
3192
|
}
|
|
3060
3193
|
|
|
3194
|
+
// Skip alias when operator name matches the CLI binary ("nexo") to avoid shadowing it
|
|
3195
|
+
const skipAlias = aliasName === "nexo";
|
|
3196
|
+
const aliasLine = skipAlias ? null : `alias ${aliasName}='nexo chat .'`;
|
|
3197
|
+
const aliasComment = skipAlias ? null : `# ${operatorName} — open the configured NEXO terminal client`;
|
|
3198
|
+
|
|
3061
3199
|
for (const rcFile of rcFiles) {
|
|
3062
3200
|
let rcContent = "";
|
|
3063
3201
|
if (fs.existsSync(rcFile)) {
|
|
@@ -3072,14 +3210,20 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3072
3210
|
log(`Runtime CLI already present in ${path.basename(rcFile)}`);
|
|
3073
3211
|
}
|
|
3074
3212
|
|
|
3075
|
-
if (!
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3213
|
+
if (!skipAlias) {
|
|
3214
|
+
if (!rcContent.includes(`alias ${aliasName}=`)) {
|
|
3215
|
+
fs.appendFileSync(rcFile, `\n${aliasComment}\n${aliasLine}\n`);
|
|
3216
|
+
log(`Added '${aliasName}' alias to ${path.basename(rcFile)}`);
|
|
3217
|
+
} else {
|
|
3218
|
+
log(`Alias '${aliasName}' already exists in ${path.basename(rcFile)}`);
|
|
3219
|
+
}
|
|
3080
3220
|
}
|
|
3081
3221
|
}
|
|
3082
|
-
|
|
3222
|
+
if (skipAlias) {
|
|
3223
|
+
log(`Operator name is 'nexo' — skipping alias (CLI binary already provides 'nexo' command)`);
|
|
3224
|
+
} else {
|
|
3225
|
+
log(`After setup, open a new terminal and type: ${aliasName} or nexo`);
|
|
3226
|
+
}
|
|
3083
3227
|
console.log("");
|
|
3084
3228
|
|
|
3085
3229
|
// Step 9: Generate CLAUDE.md template
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.20",
|
|
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",
|