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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.3.18",
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
- const mailAccounts = run("defaults read com.apple.mail MailAccounts 2>/dev/null | grep AccountName | head -20");
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(Boolean);
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 configured`);
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
- line(`${repos.length} repos \u00B7 ${profileData.ssh.length} servers \u00B7 ${profileData.email.length} email accounts`);
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 and add alias
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 (!rcContent.includes(`alias ${aliasName}=`)) {
3076
- fs.appendFileSync(rcFile, `\n${aliasComment}\n${aliasLine}\n`);
3077
- log(`Added '${aliasName}' alias to ${path.basename(rcFile)}`);
3078
- } else {
3079
- log(`Alias '${aliasName}' already exists in ${path.basename(rcFile)}`);
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
- log(`After setup, open a new terminal and type: ${aliasName} or nexo`);
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.18",
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",