nexo-brain 0.2.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO — Cognitive co-operator for Claude Code. Atkinson-Shiffrin memory, semantic RAG, trust scoring, and metacognitive error prevention.",
6
6
  "bin": {
package/src/db.py CHANGED
@@ -10,6 +10,7 @@ import pathlib
10
10
  from pathlib import Path
11
11
 
12
12
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
13
+ NEXO_HOME.mkdir(parents=True, exist_ok=True)
13
14
 
14
15
  DB_PATH = os.environ.get(
15
16
  "NEXO_TEST_DB",
@@ -20,7 +21,7 @@ DB_PATH = os.environ.get(
20
21
  )
21
22
 
22
23
  # TTLs in seconds
23
- SESSION_STALE_SECONDS = 3600 # 1 hour
24
+ SESSION_STALE_SECONDS = 900 # 15 min (documented TTL)
24
25
  MESSAGE_TTL_SECONDS = 3600 # 1 hour
25
26
  QUESTION_TTL_SECONDS = 600 # 10 min
26
27
 
package/src/server.py CHANGED
@@ -86,6 +86,16 @@ def nexo_heartbeat(sid: str, task: str) -> str:
86
86
  return handle_heartbeat(sid, task)
87
87
 
88
88
 
89
+ @mcp.tool
90
+ def nexo_stop(sid: str) -> str:
91
+ """Cleanly close a session. Removes it from active sessions immediately.
92
+
93
+ Call this when ending a conversation to avoid ghost sessions.
94
+ Args:
95
+ sid: Session ID to close."""
96
+ from tools_sessions import handle_stop
97
+ return handle_stop(sid)
98
+
89
99
  @mcp.tool
90
100
  def nexo_status(keyword: str = "") -> str:
91
101
  """List active sessions. Filter by keyword if provided."""
@@ -149,6 +149,12 @@ def handle_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
149
149
  return "\n".join(parts)
150
150
 
151
151
 
152
+ def handle_stop(sid: str) -> str:
153
+ """Cleanly close a session, removing it from active sessions immediately."""
154
+ complete_session(sid)
155
+ return f"Sesión {sid} cerrada."
156
+
157
+
152
158
  def handle_status(keyword: str | None = None) -> str:
153
159
  """List active sessions, optionally filtered by keyword."""
154
160
  clean_stale_sessions()
@@ -1,610 +0,0 @@
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: Create shell alias so user can just type the operator's name
537
- log("Creating shell alias...");
538
- const aliasName = operatorName.toLowerCase();
539
- const aliasLine = `alias ${aliasName}='claude --dangerously-skip-permissions "."'`;
540
- const aliasComment = `# ${operatorName} — start Claude Code with ${operatorName} speaking first`;
541
-
542
- // Detect shell and add alias
543
- const userShell = process.env.SHELL || "/bin/bash";
544
- const rcFile = userShell.includes("zsh")
545
- ? path.join(require("os").homedir(), ".zshrc")
546
- : path.join(require("os").homedir(), ".bash_profile");
547
-
548
- let rcContent = "";
549
- if (fs.existsSync(rcFile)) {
550
- rcContent = fs.readFileSync(rcFile, "utf8");
551
- }
552
-
553
- if (!rcContent.includes(`alias ${aliasName}=`)) {
554
- fs.appendFileSync(rcFile, `\n${aliasComment}\n${aliasLine}\n`);
555
- log(`Added '${aliasName}' alias to ${path.basename(rcFile)}`);
556
- log(`After setup, open a new terminal and type: ${aliasName}`);
557
- } else {
558
- log(`Alias '${aliasName}' already exists in ${path.basename(rcFile)}`);
559
- }
560
- console.log("");
561
-
562
- // Step 9: Generate CLAUDE.md template
563
- log("Generating operator instructions...");
564
- const templateSrc = path.join(templateDir, "CLAUDE.md.template");
565
- let claudeMd = "";
566
- if (fs.existsSync(templateSrc)) {
567
- claudeMd = fs
568
- .readFileSync(templateSrc, "utf8")
569
- .replace(/\{\{NAME\}\}/g, operatorName)
570
- .replace(/\{\{NEXO_HOME\}\}/g, NEXO_HOME);
571
- } else {
572
- claudeMd = `# ${operatorName} — Cognitive Co-Operator
573
-
574
- Instructions for ${operatorName} are generated during setup.
575
- See ~/.nexo/ for configuration.
576
- `;
577
- }
578
-
579
- // Write to user's global CLAUDE.md if it doesn't exist
580
- const userClaudeMd = path.join(require("os").homedir(), ".claude", "CLAUDE.md");
581
- if (!fs.existsSync(userClaudeMd)) {
582
- fs.writeFileSync(userClaudeMd, claudeMd);
583
- log("Created ~/.claude/CLAUDE.md with operator instructions.");
584
- } else {
585
- // Save as reference
586
- fs.writeFileSync(path.join(NEXO_HOME, "CLAUDE.md.generated"), claudeMd);
587
- log(
588
- "~/.claude/CLAUDE.md already exists. Generated template saved to ~/.nexo/CLAUDE.md.generated"
589
- );
590
- }
591
-
592
- console.log("");
593
- console.log(
594
- " ╔══════════════════════════════════════════════════════════╗"
595
- );
596
- console.log(
597
- ` ║ ${operatorName} is ready. Type '${aliasName}' to start.${" ".repeat(Math.max(0, 30 - operatorName.length - aliasName.length))}║`
598
- );
599
- console.log(
600
- " ╚══════════════════════════════════════════════════════════╝"
601
- );
602
- console.log("");
603
-
604
- rl.close();
605
- }
606
-
607
- main().catch((err) => {
608
- console.error("Setup failed:", err.message);
609
- process.exit(1);
610
- });