nexo-brain 0.1.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -0
  3. package/bin/create-nexo.js +593 -0
  4. package/package.json +32 -0
  5. package/scripts/pre-commit-check.sh +55 -0
  6. package/src/cognitive.py +1224 -0
  7. package/src/db.py +2283 -0
  8. package/src/hooks/caffeinate-guard.sh +8 -0
  9. package/src/hooks/capture-session.sh +19 -0
  10. package/src/hooks/session-start.sh +27 -0
  11. package/src/hooks/session-stop.sh +11 -0
  12. package/src/plugin_loader.py +136 -0
  13. package/src/plugins/__init__.py +0 -0
  14. package/src/plugins/agents.py +52 -0
  15. package/src/plugins/backup.py +103 -0
  16. package/src/plugins/cognitive_memory.py +305 -0
  17. package/src/plugins/entities.py +61 -0
  18. package/src/plugins/episodic_memory.py +391 -0
  19. package/src/plugins/evolution.py +113 -0
  20. package/src/plugins/guard.py +346 -0
  21. package/src/plugins/preferences.py +47 -0
  22. package/src/scripts/nexo-auto-update.py +213 -0
  23. package/src/scripts/nexo-catchup.py +179 -0
  24. package/src/scripts/nexo-cognitive-decay.py +82 -0
  25. package/src/scripts/nexo-daily-self-audit.py +532 -0
  26. package/src/scripts/nexo-postmortem-consolidator.py +594 -0
  27. package/src/scripts/nexo-sleep.py +762 -0
  28. package/src/scripts/nexo-synthesis.py +537 -0
  29. package/src/server.py +560 -0
  30. package/src/tools_coordination.py +102 -0
  31. package/src/tools_credentials.py +64 -0
  32. package/src/tools_learnings.py +180 -0
  33. package/src/tools_menu.py +208 -0
  34. package/src/tools_reminders.py +80 -0
  35. package/src/tools_reminders_crud.py +157 -0
  36. package/src/tools_sessions.py +169 -0
  37. package/src/tools_task_history.py +57 -0
  38. package/templates/CLAUDE.md.template +89 -0
@@ -0,0 +1,593 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-nexo — Interactive installer for NEXO cognitive co-operator.
4
+ *
5
+ * Usage: npx create-nexo
6
+ *
7
+ * What it does:
8
+ * 1. Asks for the co-operator's name
9
+ * 2. Asks permission to scan the workspace
10
+ * 3. Installs Python dependencies (fastembed, numpy, mcp)
11
+ * 4. Creates ~/.nexo/ with DB, personality, and config
12
+ * 5. Configures Claude Code MCP settings
13
+ * 6. Creates LaunchAgents for macOS automated processes
14
+ * 7. Generates CLAUDE.md with the operator's instructions
15
+ */
16
+
17
+ const { execSync, spawnSync } = require("child_process");
18
+ const fs = require("fs");
19
+ const path = require("path");
20
+ const readline = require("readline");
21
+
22
+ const NEXO_HOME = path.join(require("os").homedir(), ".nexo");
23
+ const CLAUDE_SETTINGS = path.join(
24
+ require("os").homedir(),
25
+ ".claude",
26
+ "settings.json"
27
+ );
28
+ const LAUNCH_AGENTS = path.join(
29
+ require("os").homedir(),
30
+ "Library",
31
+ "LaunchAgents"
32
+ );
33
+
34
+ const rl = readline.createInterface({
35
+ input: process.stdin,
36
+ output: process.stdout,
37
+ });
38
+
39
+ function ask(question) {
40
+ return new Promise((resolve) => rl.question(question, resolve));
41
+ }
42
+
43
+ function run(cmd, opts = {}) {
44
+ try {
45
+ return execSync(cmd, { encoding: "utf8", stdio: "pipe", ...opts }).trim();
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function log(msg) {
52
+ console.log(` ${msg}`);
53
+ }
54
+
55
+ async function main() {
56
+ console.log("");
57
+ console.log(
58
+ " ╔══════════════════════════════════════════════════════════╗"
59
+ );
60
+ console.log(
61
+ " ║ NEXO — Cognitive Co-Operator for Claude Code ║"
62
+ );
63
+ console.log(
64
+ " ║ Atkinson-Shiffrin Memory | RAG | Trust Score ║"
65
+ );
66
+ console.log(
67
+ " ╚══════════════════════════════════════════════════════════╝"
68
+ );
69
+ console.log("");
70
+
71
+ // Check prerequisites
72
+ const platform = process.platform;
73
+ if (platform !== "darwin") {
74
+ log("NEXO currently supports macOS only. Linux support coming soon.");
75
+ process.exit(1);
76
+ }
77
+
78
+ // Find or install Homebrew (needed for Python)
79
+ let hasBrew = run("which brew");
80
+ if (!hasBrew) {
81
+ log("Homebrew not found. Installing...");
82
+ spawnSync("/bin/bash", ["-c", '$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)'], {
83
+ stdio: "inherit",
84
+ });
85
+ hasBrew = run("which brew") || run("eval $(/opt/homebrew/bin/brew shellenv) && which brew");
86
+ }
87
+
88
+ // Find or install Python
89
+ let python = run("which python3");
90
+ if (!python) {
91
+ if (hasBrew) {
92
+ log("Python 3 not found. Installing via Homebrew...");
93
+ spawnSync("brew", ["install", "python3"], { stdio: "inherit" });
94
+ python = run("which python3");
95
+ }
96
+ if (!python) {
97
+ log("Python 3 not found and couldn't install automatically.");
98
+ log("Install it manually: brew install python3");
99
+ process.exit(1);
100
+ }
101
+ }
102
+ const pyVersion = run(`${python} --version`);
103
+ log(`Found ${pyVersion} at ${python}`);
104
+
105
+ // Find or install Claude Code
106
+ let claudeInstalled = run("which claude");
107
+ if (!claudeInstalled) {
108
+ log("Claude Code not found. Installing...");
109
+ const npmInstall = spawnSync("npm", ["install", "-g", "@anthropic-ai/claude-code"], {
110
+ stdio: "inherit",
111
+ });
112
+ claudeInstalled = run("which claude");
113
+ if (!claudeInstalled) {
114
+ log("Could not install Claude Code automatically.");
115
+ log("Install it manually: npm install -g @anthropic-ai/claude-code");
116
+ process.exit(1);
117
+ }
118
+ log("Claude Code installed successfully.");
119
+ } else {
120
+ log("Claude Code detected.");
121
+ }
122
+ console.log("");
123
+
124
+ // Step 1: Name
125
+ const name = await ask(" How should I call myself? (default: NEXO) > ");
126
+ const operatorName = name.trim() || "NEXO";
127
+ log(`Got it. I'm ${operatorName}.`);
128
+ console.log("");
129
+
130
+ // Step 2: Permission to scan
131
+ const scanAnswer = await ask(
132
+ " Can I explore your workspace to learn about your projects? (y/n) > "
133
+ );
134
+ const doScan = scanAnswer.trim().toLowerCase().startsWith("y");
135
+ console.log("");
136
+
137
+ // Step 2b: Keep Mac awake for nocturnal processes?
138
+ const caffeinateAnswer = await ask(
139
+ " Keep Mac awake so my cognitive processes run on schedule? (y/n) > "
140
+ );
141
+ const doCaffeinate = caffeinateAnswer.trim().toLowerCase().startsWith("y");
142
+ console.log("");
143
+
144
+ // Step 3: Install Python dependencies
145
+ log("Installing cognitive engine dependencies...");
146
+ const pipInstall = spawnSync(
147
+ python,
148
+ [
149
+ "-m",
150
+ "pip",
151
+ "install",
152
+ "--quiet",
153
+ "fastembed",
154
+ "numpy",
155
+ "mcp[cli]",
156
+ ],
157
+ { stdio: "inherit" }
158
+ );
159
+ if (pipInstall.status !== 0) {
160
+ log("Failed to install Python dependencies. Check pip.");
161
+ process.exit(1);
162
+ }
163
+ log("Dependencies installed.");
164
+
165
+ // Step 4: Create ~/.nexo/
166
+ log("Setting up NEXO home...");
167
+ const dirs = [
168
+ NEXO_HOME,
169
+ path.join(NEXO_HOME, "plugins"),
170
+ path.join(NEXO_HOME, "scripts"),
171
+ path.join(NEXO_HOME, "logs"),
172
+ path.join(NEXO_HOME, "backups"),
173
+ path.join(NEXO_HOME, "coordination"),
174
+ path.join(NEXO_HOME, "brain"),
175
+ ];
176
+ dirs.forEach((d) => fs.mkdirSync(d, { recursive: true }));
177
+
178
+ // Write version file for auto-update tracking
179
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
180
+ fs.writeFileSync(
181
+ path.join(NEXO_HOME, "version.json"),
182
+ JSON.stringify({
183
+ version: pkg.version,
184
+ installed_at: new Date().toISOString(),
185
+ files_updated: 0,
186
+ }, null, 2)
187
+ );
188
+
189
+ // Copy source files
190
+ const srcDir = path.join(__dirname, "..", "src");
191
+ const scriptsSrcDir = path.join(__dirname, "..", "src", "scripts");
192
+ const pluginsSrcDir = path.join(__dirname, "..", "src", "plugins");
193
+ const templateDir = path.join(__dirname, "..", "templates");
194
+
195
+ // Core files
196
+ const coreFiles = [
197
+ "server.py",
198
+ "db.py",
199
+ "plugin_loader.py",
200
+ "cognitive.py",
201
+ "tools_sessions.py",
202
+ "tools_coordination.py",
203
+ "tools_reminders.py",
204
+ "tools_reminders_crud.py",
205
+ "tools_learnings.py",
206
+ "tools_credentials.py",
207
+ "tools_task_history.py",
208
+ "tools_menu.py",
209
+ ];
210
+ coreFiles.forEach((f) => {
211
+ const src = path.join(srcDir, f);
212
+ if (fs.existsSync(src)) {
213
+ fs.copyFileSync(src, path.join(NEXO_HOME, f));
214
+ }
215
+ });
216
+
217
+ // Plugins
218
+ const pluginFiles = [
219
+ "__init__.py",
220
+ "guard.py",
221
+ "episodic_memory.py",
222
+ "cognitive_memory.py",
223
+ "entities.py",
224
+ "preferences.py",
225
+ "agents.py",
226
+ "backup.py",
227
+ "evolution.py",
228
+ ];
229
+ pluginFiles.forEach((f) => {
230
+ const src = path.join(pluginsSrcDir, f);
231
+ if (fs.existsSync(src)) {
232
+ fs.copyFileSync(src, path.join(NEXO_HOME, "plugins", f));
233
+ }
234
+ });
235
+
236
+ // Scripts
237
+ const scriptFiles = fs
238
+ .readdirSync(scriptsSrcDir || ".")
239
+ .filter((f) => f.endsWith(".py"));
240
+ scriptFiles.forEach((f) => {
241
+ const src = path.join(scriptsSrcDir, f);
242
+ if (fs.existsSync(src)) {
243
+ fs.copyFileSync(src, path.join(NEXO_HOME, "scripts", f));
244
+ }
245
+ });
246
+
247
+ // Generate personality
248
+ const personality = `# ${operatorName} — Personality
249
+
250
+ I am ${operatorName}, a cognitive co-operator. Not an assistant — an operational partner.
251
+
252
+ ## Core traits
253
+ - Direct: I say what I think, not what sounds nice
254
+ - Action-oriented: I do things, I don't suggest things
255
+ - Self-critical: I track my mistakes and learn from them
256
+ - Proactive: If I can detect or fix something without being asked, I do it
257
+
258
+ ## What I never do
259
+ - Ask the user to do something I can do myself
260
+ - Say "I can't" without trying alternatives first
261
+ - Give long explanations when a short answer suffices
262
+ - Repeat mistakes I've already logged
263
+ `;
264
+ fs.writeFileSync(path.join(NEXO_HOME, "brain", "personality.md"), personality);
265
+
266
+ // Generate user profile
267
+ const profile = `# User Profile
268
+
269
+ Created: ${new Date().toISOString().split("T")[0]}
270
+ Operator name: ${operatorName}
271
+
272
+ ## Observed preferences
273
+ (${operatorName} will learn these over time)
274
+
275
+ ## Work patterns
276
+ (${operatorName} will observe and record these)
277
+ `;
278
+ fs.writeFileSync(path.join(NEXO_HOME, "brain", "user-profile.md"), profile);
279
+
280
+ // Step 5: Scan workspace
281
+ if (doScan) {
282
+ log("Scanning workspace...");
283
+ const cwd = process.cwd();
284
+ const findings = [];
285
+
286
+ // Git repos
287
+ const gitDirs = run(
288
+ `find "${cwd}" -maxdepth 3 -name ".git" -type d 2>/dev/null`
289
+ );
290
+ if (gitDirs) {
291
+ const repos = gitDirs.split("\n").filter(Boolean);
292
+ findings.push(`${repos.length} git repositories`);
293
+ }
294
+
295
+ // Package managers
296
+ if (fs.existsSync(path.join(cwd, "package.json")))
297
+ findings.push("Node.js project detected");
298
+ if (fs.existsSync(path.join(cwd, "requirements.txt")))
299
+ findings.push("Python project detected");
300
+ if (fs.existsSync(path.join(cwd, "Cargo.toml")))
301
+ findings.push("Rust project detected");
302
+ if (fs.existsSync(path.join(cwd, "go.mod")))
303
+ findings.push("Go project detected");
304
+
305
+ // Config files
306
+ if (fs.existsSync(path.join(cwd, ".env")))
307
+ findings.push(".env file found (will NOT read contents)");
308
+
309
+ if (findings.length > 0) {
310
+ log("Found:");
311
+ findings.forEach((f) => log(` - ${f}`));
312
+ } else {
313
+ log("No projects detected in current directory.");
314
+ }
315
+
316
+ // Save scan results
317
+ fs.writeFileSync(
318
+ path.join(NEXO_HOME, "brain", "workspace-scan.json"),
319
+ JSON.stringify(
320
+ { scanned_at: new Date().toISOString(), cwd, findings },
321
+ null,
322
+ 2
323
+ )
324
+ );
325
+ }
326
+
327
+ console.log("");
328
+
329
+ // Step 6: Configure Claude Code MCP
330
+ log("Configuring Claude Code MCP server...");
331
+ let settings = {};
332
+ if (fs.existsSync(CLAUDE_SETTINGS)) {
333
+ try {
334
+ settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS, "utf8"));
335
+ } catch {
336
+ settings = {};
337
+ }
338
+ }
339
+
340
+ if (!settings.mcpServers) settings.mcpServers = {};
341
+ settings.mcpServers.nexo = {
342
+ command: python,
343
+ args: [path.join(NEXO_HOME, "server.py")],
344
+ env: {
345
+ NEXO_HOME: NEXO_HOME,
346
+ NEXO_NAME: operatorName,
347
+ },
348
+ };
349
+
350
+ // Configure hooks for session capture (Sensory Register)
351
+ if (!settings.hooks) settings.hooks = {};
352
+
353
+ // Copy hook scripts to NEXO_HOME
354
+ const hooksSrcDir = path.join(__dirname, "..", "src", "hooks");
355
+ const hooksDestDir = path.join(NEXO_HOME, "hooks");
356
+ fs.mkdirSync(hooksDestDir, { recursive: true });
357
+ ["session-start.sh", "capture-session.sh", "session-stop.sh"].forEach((h) => {
358
+ const src = path.join(hooksSrcDir, h);
359
+ const dest = path.join(hooksDestDir, h);
360
+ if (fs.existsSync(src)) {
361
+ fs.copyFileSync(src, dest);
362
+ fs.chmodSync(dest, "755");
363
+ }
364
+ });
365
+
366
+ // SessionStart hook
367
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
368
+ const startHook = {
369
+ type: "command",
370
+ command: `bash ${path.join(hooksDestDir, "session-start.sh")}`,
371
+ };
372
+ if (!settings.hooks.SessionStart.some((h) => h.command && h.command.includes("session-start.sh"))) {
373
+ settings.hooks.SessionStart.push(startHook);
374
+ }
375
+
376
+ // PostToolUse hook (captures tool usage to session_buffer)
377
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
378
+ const captureHook = {
379
+ type: "command",
380
+ command: `bash ${path.join(hooksDestDir, "capture-session.sh")}`,
381
+ };
382
+ if (!settings.hooks.PostToolUse.some((h) => h.command && h.command.includes("capture-session.sh"))) {
383
+ settings.hooks.PostToolUse.push(captureHook);
384
+ }
385
+
386
+ // Stop hook (session end)
387
+ if (!settings.hooks.Stop) settings.hooks.Stop = [];
388
+ const stopHook = {
389
+ type: "command",
390
+ command: `bash ${path.join(hooksDestDir, "session-stop.sh")}`,
391
+ };
392
+ if (!settings.hooks.Stop.some((h) => h.command && h.command.includes("session-stop.sh"))) {
393
+ settings.hooks.Stop.push(stopHook);
394
+ }
395
+
396
+ const settingsDir = path.dirname(CLAUDE_SETTINGS);
397
+ fs.mkdirSync(settingsDir, { recursive: true });
398
+ fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
399
+ log("MCP server + hooks configured in Claude Code settings.");
400
+
401
+ // Step 7: Install LaunchAgents
402
+ log("Setting up automated processes...");
403
+ fs.mkdirSync(LAUNCH_AGENTS, { recursive: true });
404
+
405
+ const agents = [
406
+ {
407
+ name: "cognitive-decay",
408
+ script: "nexo-cognitive-decay.py",
409
+ hour: 3,
410
+ minute: 0,
411
+ },
412
+ {
413
+ name: "postmortem",
414
+ script: "nexo-postmortem-consolidator.py",
415
+ hour: 23,
416
+ minute: 30,
417
+ },
418
+ {
419
+ name: "sleep",
420
+ script: "nexo-sleep.py",
421
+ hour: 4,
422
+ minute: 0,
423
+ },
424
+ {
425
+ name: "self-audit",
426
+ script: "nexo-daily-self-audit.py",
427
+ hour: 7,
428
+ minute: 0,
429
+ },
430
+ { name: "catchup", script: "nexo-catchup.py", runAtLoad: true },
431
+ ];
432
+
433
+ agents.forEach((agent) => {
434
+ const plistName = `com.nexo.${agent.name}.plist`;
435
+ const plistPath = path.join(LAUNCH_AGENTS, plistName);
436
+
437
+ let scheduleBlock = "";
438
+ if (agent.runAtLoad) {
439
+ scheduleBlock = ` <key>RunAtLoad</key>
440
+ <true/>`;
441
+ } else {
442
+ scheduleBlock = ` <key>StartCalendarInterval</key>
443
+ <dict>
444
+ <key>Hour</key>
445
+ <integer>${agent.hour}</integer>
446
+ <key>Minute</key>
447
+ <integer>${agent.minute}</integer>
448
+ </dict>
449
+ <key>RunAtLoad</key>
450
+ <false/>`;
451
+ }
452
+
453
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
454
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
455
+ <plist version="1.0">
456
+ <dict>
457
+ <key>Label</key>
458
+ <string>com.nexo.${agent.name}</string>
459
+ <key>ProgramArguments</key>
460
+ <array>
461
+ <string>${python}</string>
462
+ <string>${path.join(NEXO_HOME, "scripts", agent.script)}</string>
463
+ </array>
464
+ ${scheduleBlock}
465
+ <key>StandardOutPath</key>
466
+ <string>${path.join(NEXO_HOME, "logs", `${agent.name}-stdout.log`)}</string>
467
+ <key>StandardErrorPath</key>
468
+ <string>${path.join(NEXO_HOME, "logs", `${agent.name}-stderr.log`)}</string>
469
+ <key>EnvironmentVariables</key>
470
+ <dict>
471
+ <key>HOME</key>
472
+ <string>${require("os").homedir()}</string>
473
+ <key>NEXO_HOME</key>
474
+ <string>${NEXO_HOME}</string>
475
+ <key>PATH</key>
476
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
477
+ </dict>
478
+ </dict>
479
+ </plist>`;
480
+
481
+ fs.writeFileSync(plistPath, plist);
482
+ // Register the agent
483
+ try {
484
+ execSync(
485
+ `launchctl bootout gui/$(id -u) "${plistPath}" 2>/dev/null; launchctl bootstrap gui/$(id -u) "${plistPath}"`,
486
+ { stdio: "pipe" }
487
+ );
488
+ } catch {
489
+ // May fail if not previously loaded, that's OK
490
+ }
491
+ });
492
+ log(`${agents.length} automated processes configured.`);
493
+
494
+ // Caffeinate: keep Mac awake for nocturnal processes
495
+ if (doCaffeinate) {
496
+ const caffHookSrc = path.join(__dirname, "..", "src", "hooks", "caffeinate-guard.sh");
497
+ const caffHookDest = path.join(NEXO_HOME, "hooks", "caffeinate-guard.sh");
498
+ if (fs.existsSync(caffHookSrc)) {
499
+ fs.copyFileSync(caffHookSrc, caffHookDest);
500
+ fs.chmodSync(caffHookDest, "755");
501
+ }
502
+
503
+ const caffPlist = `<?xml version="1.0" encoding="UTF-8"?>
504
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
505
+ <plist version="1.0">
506
+ <dict>
507
+ <key>Label</key>
508
+ <string>com.nexo.caffeinate</string>
509
+ <key>ProgramArguments</key>
510
+ <array>
511
+ <string>/bin/bash</string>
512
+ <string>${caffHookDest}</string>
513
+ </array>
514
+ <key>RunAtLoad</key>
515
+ <true/>
516
+ <key>KeepAlive</key>
517
+ <true/>
518
+ <key>StandardOutPath</key>
519
+ <string>${path.join(NEXO_HOME, "logs", "caffeinate-stdout.log")}</string>
520
+ <key>StandardErrorPath</key>
521
+ <string>${path.join(NEXO_HOME, "logs", "caffeinate-stderr.log")}</string>
522
+ </dict>
523
+ </plist>`;
524
+
525
+ const caffPlistPath = path.join(LAUNCH_AGENTS, "com.nexo.caffeinate.plist");
526
+ fs.writeFileSync(caffPlistPath, caffPlist);
527
+ try {
528
+ execSync(
529
+ `launchctl bootout gui/$(id -u) "${caffPlistPath}" 2>/dev/null; launchctl bootstrap gui/$(id -u) "${caffPlistPath}"`,
530
+ { stdio: "pipe" }
531
+ );
532
+ } catch {}
533
+ log("Caffeinate enabled — Mac will stay awake for cognitive processes.");
534
+ }
535
+
536
+ // Step 8: Generate CLAUDE.md template
537
+ log("Generating operator instructions...");
538
+ const templateSrc = path.join(templateDir, "CLAUDE.md.template");
539
+ let claudeMd = "";
540
+ if (fs.existsSync(templateSrc)) {
541
+ claudeMd = fs
542
+ .readFileSync(templateSrc, "utf8")
543
+ .replace(/\{\{NAME\}\}/g, operatorName)
544
+ .replace(/\{\{NEXO_HOME\}\}/g, NEXO_HOME);
545
+ } else {
546
+ claudeMd = `# ${operatorName} — Cognitive Co-Operator
547
+
548
+ Instructions for ${operatorName} are generated during setup.
549
+ See ~/.nexo/ for configuration.
550
+ `;
551
+ }
552
+
553
+ // Write to user's global CLAUDE.md if it doesn't exist
554
+ const userClaudeMd = path.join(require("os").homedir(), ".claude", "CLAUDE.md");
555
+ if (!fs.existsSync(userClaudeMd)) {
556
+ fs.writeFileSync(userClaudeMd, claudeMd);
557
+ log("Created ~/.claude/CLAUDE.md with operator instructions.");
558
+ } else {
559
+ // Save as reference
560
+ fs.writeFileSync(path.join(NEXO_HOME, "CLAUDE.md.generated"), claudeMd);
561
+ log(
562
+ "~/.claude/CLAUDE.md already exists. Generated template saved to ~/.nexo/CLAUDE.md.generated"
563
+ );
564
+ }
565
+
566
+ console.log("");
567
+ console.log(
568
+ " ╔══════════════════════════════════════════════════════════╗"
569
+ );
570
+ console.log(
571
+ ` ║ ${operatorName} is ready.${" ".repeat(Math.max(0, 39 - operatorName.length))}║`
572
+ );
573
+ console.log(
574
+ " ║ ║"
575
+ );
576
+ console.log(
577
+ " ║ Open Claude Code and start a conversation. ║"
578
+ );
579
+ console.log(
580
+ ` ║ ${operatorName} will introduce ${operatorName.length > 4 ? "itself" : "itself"} on first message.${" ".repeat(Math.max(0, 27 - operatorName.length))}║`
581
+ );
582
+ console.log(
583
+ " ╚══════════════════════════════════════════════════════════╝"
584
+ );
585
+ console.log("");
586
+
587
+ rl.close();
588
+ }
589
+
590
+ main().catch((err) => {
591
+ console.error("Setup failed:", err.message);
592
+ process.exit(1);
593
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "nexo-brain",
3
+ "version": "0.1.0",
4
+ "description": "NEXO — Cognitive co-operator for Claude Code. Atkinson-Shiffrin memory, semantic RAG, trust scoring, and metacognitive error prevention.",
5
+ "bin": {
6
+ "nexo-brain": "./bin/create-nexo.js"
7
+ },
8
+ "keywords": [
9
+ "claude-code",
10
+ "mcp",
11
+ "cognitive-architecture",
12
+ "memory",
13
+ "ai-assistant",
14
+ "vector-search",
15
+ "atkinson-shiffrin"
16
+ ],
17
+ "author": "Francisco Garcia <hello@wazion.com>",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/wazionapps/nexo.git"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "files": [
27
+ "bin/",
28
+ "src/",
29
+ "templates/",
30
+ "scripts/"
31
+ ]
32
+ }
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+ # Pre-commit hook: prevent private data from being committed to the public repo.
3
+ # Installed by create-nexo or manually: cp scripts/pre-commit-check.sh .git/hooks/pre-commit
4
+
5
+ RED='\033[0;31m'
6
+ NC='\033[0m'
7
+
8
+ # Add patterns specific to your private data here.
9
+ # These are checked against staged files to prevent accidental leaks.
10
+ # The pre-commit-check.sh script itself is excluded from scanning.
11
+ BLOCKED_PATTERNS=(
12
+ # Add your own patterns below, e.g.:
13
+ # "my-private-api-key"
14
+ # "my-private-domain.com"
15
+ # "my-server-ip"
16
+ )
17
+
18
+ STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
19
+
20
+ if [ -z "$STAGED_FILES" ]; then
21
+ exit 0
22
+ fi
23
+
24
+ FOUND=0
25
+ for pattern in "${BLOCKED_PATTERNS[@]}"; do
26
+ MATCHES=$(echo "$STAGED_FILES" | xargs grep -l "$pattern" 2>/dev/null)
27
+ if [ -n "$MATCHES" ]; then
28
+ echo -e "${RED}BLOCKED: Found private data pattern '$pattern' in:${NC}"
29
+ echo "$MATCHES" | sed 's/^/ /'
30
+ FOUND=1
31
+ fi
32
+ done
33
+
34
+ # Also check for .db files, tokens, credentials
35
+ DB_FILES=$(echo "$STAGED_FILES" | grep -E '\.(db|db-wal|db-shm|key|pem)$')
36
+ if [ -n "$DB_FILES" ]; then
37
+ echo -e "${RED}BLOCKED: Database/key files staged:${NC}"
38
+ echo "$DB_FILES" | sed 's/^/ /'
39
+ FOUND=1
40
+ fi
41
+
42
+ TOKEN_FILES=$(echo "$STAGED_FILES" | grep -E '_token\.|credentials|\.env$')
43
+ if [ -n "$TOKEN_FILES" ]; then
44
+ echo -e "${RED}BLOCKED: Token/credential files staged:${NC}"
45
+ echo "$TOKEN_FILES" | sed 's/^/ /'
46
+ FOUND=1
47
+ fi
48
+
49
+ if [ $FOUND -eq 1 ]; then
50
+ echo ""
51
+ echo -e "${RED}Commit blocked. Remove private data before pushing to public repo.${NC}"
52
+ exit 1
53
+ fi
54
+
55
+ exit 0