nexo-brain 2.4.0 → 2.5.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/README.md +80 -4
- package/bin/nexo-brain.js +238 -12
- package/bin/nexo.js +55 -0
- package/community/skills/.gitkeep +1 -0
- package/package.json +11 -3
- package/src/auto_update.py +193 -9
- package/src/cli.py +719 -0
- package/src/cognitive/_ingest.py +1 -1
- package/src/cognitive/_memory.py +4 -4
- package/src/crons/manifest.json +8 -0
- package/src/dashboard/app.py +700 -35
- package/src/dashboard/templates/adaptive.html +112 -218
- package/src/dashboard/templates/artifacts.html +133 -0
- package/src/dashboard/templates/backups.html +136 -0
- package/src/dashboard/templates/base.html +413 -0
- package/src/dashboard/templates/calendar.html +523 -654
- package/src/dashboard/templates/chat.html +356 -0
- package/src/dashboard/templates/claims.html +259 -0
- package/src/dashboard/templates/cortex.html +262 -0
- package/src/dashboard/templates/credentials.html +128 -0
- package/src/dashboard/templates/crons.html +370 -0
- package/src/dashboard/templates/dashboard.html +383 -578
- package/src/dashboard/templates/dreams.html +252 -0
- package/src/dashboard/templates/email.html +160 -0
- package/src/dashboard/templates/evolution.html +189 -0
- package/src/dashboard/templates/feed.html +249 -0
- package/src/dashboard/templates/followup_health.html +170 -0
- package/src/dashboard/templates/graph.html +191 -269
- package/src/dashboard/templates/guard.html +259 -0
- package/src/dashboard/templates/inbox.html +220 -346
- package/src/dashboard/templates/memory.html +317 -197
- package/src/dashboard/templates/operations.html +521 -698
- package/src/dashboard/templates/plugins.html +185 -0
- package/src/dashboard/templates/rules.html +246 -0
- package/src/dashboard/templates/sentiment.html +247 -0
- package/src/dashboard/templates/sessions.html +215 -182
- package/src/dashboard/templates/skills.html +329 -0
- package/src/dashboard/templates/somatic.html +68 -172
- package/src/dashboard/templates/triggers.html +133 -0
- package/src/dashboard/templates/trust.html +360 -0
- package/src/db/__init__.py +5 -0
- package/src/db/_schema.py +16 -1
- package/src/db/_sessions.py +22 -0
- package/src/db/_skills.py +980 -274
- package/src/doctor/__init__.py +1 -0
- package/src/doctor/formatters.py +52 -0
- package/src/doctor/models.py +44 -0
- package/src/doctor/orchestrator.py +42 -0
- package/src/doctor/providers/__init__.py +1 -0
- package/src/doctor/providers/boot.py +206 -0
- package/src/doctor/providers/deep.py +292 -0
- package/src/doctor/providers/runtime.py +686 -0
- package/src/evolution_cycle.py +86 -6
- package/src/hooks/post-compact.sh +5 -1
- package/src/hooks/pre-compact.sh +1 -1
- package/src/plugins/doctor.py +36 -0
- package/src/plugins/evolution.py +11 -3
- package/src/plugins/skills.py +135 -175
- package/src/requirements.txt +1 -0
- package/src/script_registry.py +322 -0
- package/src/scripts/deep-sleep/apply_findings.py +63 -48
- package/src/scripts/deep-sleep/extract-prompt.md +14 -0
- package/src/scripts/deep-sleep/synthesize-prompt.md +36 -0
- package/src/scripts/deep-sleep/synthesize.py +37 -1
- package/src/scripts/nexo-dashboard.sh +29 -0
- package/src/scripts/nexo-day-orchestrator.sh +139 -0
- package/src/scripts/nexo-evolution-run.py +141 -54
- package/src/scripts/nexo-learning-housekeep.py +1 -1
- package/src/scripts/nexo-watchdog.sh +1 -1
- package/src/server.py +9 -5
- package/src/skills/run-runtime-doctor/guide.md +12 -0
- package/src/skills/run-runtime-doctor/script.py +21 -0
- package/src/skills/run-runtime-doctor/skill.json +25 -0
- package/src/skills_runtime.py +347 -0
- package/src/tools_menu.py +3 -2
- package/src/tools_sessions.py +126 -0
- package/src/user_context.py +46 -0
- package/templates/nexo_helper.py +45 -0
- package/templates/script-template.py +44 -0
- package/templates/skill-script-template.py +39 -0
- package/templates/skill-template.md +33 -0
package/README.md
CHANGED
|
@@ -341,7 +341,7 @@ Deep Sleep generates a `session-tone.json` that tells NEXO how to behave next mo
|
|
|
341
341
|
|
|
342
342
|
This is read by `nexo_smart_startup` and injected into every session's context. NEXO adapts its personality based on real behavioral data, not just configuration.
|
|
343
343
|
|
|
344
|
-
## Cron Manifest (v2.
|
|
344
|
+
## Cron Manifest & Scheduler (v2.4.0)
|
|
345
345
|
|
|
346
346
|
All core crons are defined in `src/crons/manifest.json`. When you run `nexo_update`, the sync script:
|
|
347
347
|
- **Installs** new crons from the manifest
|
|
@@ -349,7 +349,23 @@ All core crons are defined in `src/crons/manifest.json`. When you run `nexo_upda
|
|
|
349
349
|
- **Removes** crons no longer in the manifest (only core ones)
|
|
350
350
|
- **Never touches** personal crons you created yourself
|
|
351
351
|
|
|
352
|
-
|
|
352
|
+
Every cron execution is tracked in the `cron_runs` table via a universal wrapper. Use `nexo_schedule_status` to see what ran overnight:
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
✅ deep-sleep: 1/1 OK, 4523s avg — 37 sessions, 259 findings
|
|
356
|
+
✅ immune: 48/48 OK, 2s avg
|
|
357
|
+
❌ evolution: 0/1 OK — CLI timeout
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Add personal crons from conversation with `nexo_schedule_add` — generates LaunchAgent (macOS) or systemd timer (Linux) automatically.
|
|
361
|
+
|
|
362
|
+
## Skill Auto-Creation (v2.4.0)
|
|
363
|
+
|
|
364
|
+
Deep Sleep automatically extracts reusable procedures from successful multi-step tasks and stores them as skills with full procedural content (steps, gotchas, markdown).
|
|
365
|
+
|
|
366
|
+
Pipeline: `trace → draft → published → archived`. Trust rises with successful use, decays without it. No human approval gates.
|
|
367
|
+
|
|
368
|
+
7 MCP tools: `nexo_skill_create`, `nexo_skill_match`, `nexo_skill_get`, `nexo_skill_result`, `nexo_skill_list`, `nexo_skill_merge`, `nexo_skill_stats`.
|
|
353
369
|
|
|
354
370
|
## Dashboard (v1.6.0)
|
|
355
371
|
|
|
@@ -519,8 +535,12 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
|
|
|
519
535
|
| MCP server | 147+ tools for memory, cognition, learning, guard | NEXO_HOME/ |
|
|
520
536
|
| Plugins | Guard, episodic memory, cognitive memory, entities, preferences, update, etc. | Code: src/plugins/, Personal: NEXO_HOME/plugins/ |
|
|
521
537
|
| Hooks (7) | SessionStart, Stop, PostToolUse, PreCompact, PostCompact | NEXO_HOME/hooks/ |
|
|
522
|
-
| Nervous system |
|
|
523
|
-
| Dashboard | Web UI at localhost:6174 (
|
|
538
|
+
| Nervous system | 17 autonomous processes (decay, sleep, audit, evolution, watchdog, orchestrator, dashboard, etc.) | NEXO_HOME/scripts/ |
|
|
539
|
+
| Dashboard | Web UI at localhost:6174 (23 modules, dark theme) — opt-in, always-on | NEXO_HOME/dashboard/ |
|
|
540
|
+
| Runtime CLI | `nexo` command: scripts, doctor, skills, update | NEXO_HOME/bin/ |
|
|
541
|
+
| Doctor | Unified diagnostics: boot/runtime/deep tiers, `--fix` mode | src/doctor/ |
|
|
542
|
+
| Skills v2 | Executable skills with guide/execute/hybrid modes, approval levels | NEXO_HOME/skills/ |
|
|
543
|
+
| Day Orchestrator | Autonomous cycles every 15 min (8:00-23:00) — opt-in | LaunchAgent |
|
|
524
544
|
| CLAUDE.md | Complete operator instructions (Codex, hooks, guard, trust, memory) | ~/.claude/CLAUDE.md |
|
|
525
545
|
| Schedule config | schedule.json with customizable process times and timezone | NEXO_HOME/config/ |
|
|
526
546
|
| Auto-update | Non-blocking startup check (5s max), opt-out via schedule.json | Built into server startup |
|
|
@@ -528,6 +548,38 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
|
|
|
528
548
|
| Auto-diary | 3-layer system: PostToolUse every 10 calls, PreCompact emergency, heartbeat DIARY_OVERDUE | Built into hooks |
|
|
529
549
|
| Claude Code config | MCP server + 7 hooks + 15 processes registered | ~/.claude/settings.json |
|
|
530
550
|
|
|
551
|
+
### Runtime CLI
|
|
552
|
+
|
|
553
|
+
After installation or auto-update, NEXO adds `NEXO_HOME/bin` to your shell `PATH`. Open a new terminal and the `nexo` command provides operational tools:
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
# Personal Scripts
|
|
557
|
+
nexo scripts list # List your personal scripts
|
|
558
|
+
nexo scripts run my-script # Run a script with injected NEXO env
|
|
559
|
+
nexo scripts doctor # Validate all personal scripts
|
|
560
|
+
nexo scripts call nexo_learning_search --input '{"query":"cron"}' # Call any MCP tool
|
|
561
|
+
|
|
562
|
+
# Skills v2
|
|
563
|
+
nexo skills sync # Sync filesystem skill definitions into SQLite
|
|
564
|
+
nexo skills list # List published/stable skills
|
|
565
|
+
nexo skills get SK-... # Inspect a skill definition
|
|
566
|
+
nexo skills apply SK-... --dry-run --json # Resolve guide/execute/hybrid without running it
|
|
567
|
+
nexo skills approve SK-... --execution-level local --approved-by Francisco # Optional metadata override
|
|
568
|
+
nexo skills evolution # Show text→script and improvement candidates
|
|
569
|
+
|
|
570
|
+
# Unified Doctor
|
|
571
|
+
nexo doctor # Quick boot diagnostics
|
|
572
|
+
nexo doctor --tier all # Full system check (boot + runtime + deep)
|
|
573
|
+
nexo doctor --tier runtime --json # Machine-readable health report
|
|
574
|
+
nexo doctor --fix # Apply deterministic repairs
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Personal scripts live in `NEXO_HOME/scripts/` with inline metadata. See `docs/writing-scripts.md` for details.
|
|
578
|
+
|
|
579
|
+
Skills v2 combine procedural guides with optional executable scripts. Personal skills live in `NEXO_HOME/skills/`, packaged core skills live in `NEXO_CODE/skills/` during development and `NEXO_HOME/skills-core/` in installed environments, and staged runtime copies live in `NEXO_HOME/skills-runtime/`. Execution is fully autonomous: Deep Sleep can evolve mature guide skills into executable drafts automatically, and runtime execution no longer waits for manual approval. See `docs/skills-v2.md` for the full model.
|
|
580
|
+
|
|
581
|
+
The Doctor system reads existing health artifacts (immune, watchdog, self-audit) without triggering repairs in default mode.
|
|
582
|
+
|
|
531
583
|
### Requirements
|
|
532
584
|
|
|
533
585
|
- **macOS or Linux** (Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))
|
|
@@ -741,6 +793,30 @@ If NEXO Brain is useful to you, consider:
|
|
|
741
793
|
|
|
742
794
|
## Changelog
|
|
743
795
|
|
|
796
|
+
### v2.5.0 — Runtime CLI, Doctor, Skills v2, Day Orchestrator (2026-04-03)
|
|
797
|
+
- **Runtime CLI** (`nexo`): New operational CLI separate from installer. `nexo scripts list/run/doctor/call` for personal scripts, `nexo doctor` for diagnostics, `nexo skills apply` for executable skills, `nexo update` for one-step sync.
|
|
798
|
+
- **Unified Doctor**: Modular diagnostic system with boot/runtime/deep tiers. Report-only by default, deterministic `--fix` mode. MCP tool `nexo_doctor`. LaunchAgent schedule drift detection and reconciliation.
|
|
799
|
+
- **Skills v2**: Executable skills with guide/execute/hybrid modes. Security levels (read-only/local/remote) with explicit approval. Core vs personal vs community directories. Deep Sleep auto-evolution integration.
|
|
800
|
+
- **Day Orchestrator**: Autonomous NEXO cycles every 15 min (8:00-23:00). Launches Claude Code headless with full MCP. Checks followups, emails, infra — acts autonomously, emails user only when needed. Opt-in.
|
|
801
|
+
- **Dashboard always-on**: Web UI at localhost:6174 as persistent LaunchAgent. 23 modules, Jinja2 templating, dark theme. Opt-in.
|
|
802
|
+
- **Personal Scripts Framework**: Auto-discovery in NEXO_HOME/scripts/, inline metadata, runtime detection, forbidden-pattern validation, vendorable helper, template.
|
|
803
|
+
- Configurable operator name (UserContext singleton), watchdog normalized to 30 min, LaunchAgent drift fix.
|
|
804
|
+
|
|
805
|
+
### v2.4.0 — Skills, Cron Scheduler, Security, Full Audit (2026-04-03)
|
|
806
|
+
- **Skill Auto-Creation**: Deep Sleep extracts reusable procedures from sessions. Content stored as markdown with steps and gotchas. Trust pipeline with autonomous quality control.
|
|
807
|
+
- **Cron Scheduler**: execution tracking (`cron_runs` table), `nexo_schedule_status` and `nexo_schedule_add` MCP tools, universal cron wrapper for all processes.
|
|
808
|
+
- **Deep Sleep v2.4**: watermark-based collection (late-night sessions included), per-session checkpointing (crash-safe), retry x3, JSON parsing fix, auto-calibration of personality settings.
|
|
809
|
+
- **Security**: credential redaction in tool logs, transcript sanitization, command injection fix in dashboard, path traversal protection in plugin loader.
|
|
810
|
+
- **Diary filter**: startup only shows human sessions, auto-closed cron sessions filtered out. Email sessions preserved as real interactions.
|
|
811
|
+
- **Preflight CI**: 66 automated checks (py_compile, bash -n, manifest consistency, npm artifact, forbidden markers).
|
|
812
|
+
- **Python 3.9 compat**: `from __future__ import annotations` across 18 files.
|
|
813
|
+
- **Linux**: full systemd timer support, .bashrc alias for interactive shells.
|
|
814
|
+
- Passed 5-phase automated audit: Product, Failure, Security, Packaging, UX.
|
|
815
|
+
|
|
816
|
+
### v2.2.0 — Trust Score v2 (2026-04-01)
|
|
817
|
+
- **Trust Score**: fair daily calibration from Deep Sleep analysis. Score 0-100 based on corrections, autonomy, proactivity.
|
|
818
|
+
- **Cognitive Quarantine**: new memories go through quarantine before promotion to LTM.
|
|
819
|
+
|
|
744
820
|
### v2.0.0 — Unified Architecture (2026-03-31)
|
|
745
821
|
- **Code/data separation**: Code in repo (`src/`), personal data in `NEXO_HOME` (default `~/.nexo/`). `NEXO_HOME` env var required.
|
|
746
822
|
- **Plugin loader dual-directory**: Scans `src/plugins/` (base) then `NEXO_HOME/plugins/` (personal override by filename).
|
package/bin/nexo-brain.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
const { execSync, spawnSync } = require("child_process");
|
|
18
|
+
const crypto = require("crypto");
|
|
18
19
|
const fs = require("fs");
|
|
19
20
|
const path = require("path");
|
|
20
21
|
const readline = require("readline");
|
|
@@ -31,6 +32,13 @@ const LAUNCH_AGENTS = path.join(
|
|
|
31
32
|
"LaunchAgents"
|
|
32
33
|
);
|
|
33
34
|
|
|
35
|
+
function isEphemeralInstall(nexoHome) {
|
|
36
|
+
const homeDir = require("os").homedir();
|
|
37
|
+
const allowEphemeral = process.env.NEXO_ALLOW_EPHEMERAL_INSTALL === "1";
|
|
38
|
+
if (allowEphemeral) return false;
|
|
39
|
+
return nexoHome.startsWith("/tmp/") || homeDir.startsWith("/tmp/");
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
const rl = readline.createInterface({
|
|
35
43
|
input: process.stdin,
|
|
36
44
|
output: process.stdout,
|
|
@@ -52,6 +60,33 @@ function log(msg) {
|
|
|
52
60
|
console.log(` ${msg}`);
|
|
53
61
|
}
|
|
54
62
|
|
|
63
|
+
function syncWatchdogHashRegistry(nexoHome) {
|
|
64
|
+
try {
|
|
65
|
+
const watchdogPath = path.join(nexoHome, "scripts", "nexo-watchdog.sh");
|
|
66
|
+
if (!fs.existsSync(watchdogPath)) return;
|
|
67
|
+
|
|
68
|
+
const registryPath = path.join(nexoHome, "scripts", ".watchdog-hashes");
|
|
69
|
+
const entries = new Map();
|
|
70
|
+
if (fs.existsSync(registryPath)) {
|
|
71
|
+
for (const line of fs.readFileSync(registryPath, "utf8").split(/\r?\n/)) {
|
|
72
|
+
if (!line.includes("|")) continue;
|
|
73
|
+
const [filePath, expectedHash] = line.split("|");
|
|
74
|
+
if (filePath) entries.set(filePath, expectedHash || "");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const digest = crypto.createHash("sha256").update(fs.readFileSync(watchdogPath)).digest("hex");
|
|
79
|
+
entries.set(watchdogPath, digest);
|
|
80
|
+
const body = Array.from(entries.entries())
|
|
81
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
82
|
+
.map(([filePath, hash]) => `${filePath}|${hash}`)
|
|
83
|
+
.join("\n");
|
|
84
|
+
fs.writeFileSync(registryPath, `${body}\n`);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
log(`WARN: could not sync watchdog hash registry: ${err.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
56
91
|
// CORE PROCESS & HOOK DEFINITIONS
|
|
57
92
|
// All 13 nightly/periodic processes and all 8 core hooks that make NEXO functional.
|
|
@@ -66,9 +101,9 @@ const ALL_PROCESSES = [
|
|
|
66
101
|
// --- Every 5 minutes ---
|
|
67
102
|
{ name: "auto-close-sessions", script: "auto_close_sessions.py", interpreter: "python", scriptDir: "root",
|
|
68
103
|
type: "interval", intervalMinutes: 5, purpose: "Clean stale sessions" },
|
|
69
|
-
{ name: "watchdog", script: "nexo-watchdog.sh", interpreter: "bash", scriptDir: "scripts",
|
|
70
|
-
type: "interval", intervalMinutes: 5, purpose: "Health monitoring" },
|
|
71
104
|
// --- Every 30 minutes ---
|
|
105
|
+
{ name: "watchdog", script: "nexo-watchdog.sh", interpreter: "bash", scriptDir: "scripts",
|
|
106
|
+
type: "interval", intervalMinutes: 30, purpose: "Health monitoring" },
|
|
72
107
|
{ name: "immune", script: "nexo-immune.py", interpreter: "python", scriptDir: "scripts",
|
|
73
108
|
type: "interval", intervalMinutes: 30, purpose: "System immunity checks" },
|
|
74
109
|
// --- Every 2 hours ---
|
|
@@ -86,6 +121,10 @@ const ALL_PROCESSES = [
|
|
|
86
121
|
// --- KeepAlive (persistent daemon) ---
|
|
87
122
|
{ name: "prevent-sleep", script: "nexo-prevent-sleep.sh", interpreter: "bash", scriptDir: "scripts",
|
|
88
123
|
type: "keepAlive", purpose: "Keep machine awake for nocturnal processes" },
|
|
124
|
+
{ name: "dashboard", script: "nexo-dashboard.sh", interpreter: "bash", scriptDir: "scripts",
|
|
125
|
+
type: "keepAlive", optional: "dashboard", purpose: "Web dashboard at localhost:6174" },
|
|
126
|
+
{ name: "day-orchestrator", script: "nexo-day-orchestrator.sh", interpreter: "bash", scriptDir: "scripts",
|
|
127
|
+
type: "keepAlive", optional: "orchestrator", purpose: "Autonomous NEXO cycles every 15 min (8:00-23:00)" },
|
|
89
128
|
// --- Daily (times from schedule.json) ---
|
|
90
129
|
{ name: "cognitive-decay", script: "nexo-cognitive-decay.py", interpreter: "python", scriptDir: "scripts",
|
|
91
130
|
type: "daily", defaultHour: 3, defaultMinute: 0, purpose: "Memory decay" },
|
|
@@ -262,7 +301,7 @@ const DAY_MAP = {
|
|
|
262
301
|
* Linux+systemd: .service + .timer files
|
|
263
302
|
* Linux fallback: crontab entries
|
|
264
303
|
*/
|
|
265
|
-
function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir) {
|
|
304
|
+
function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAgentsDir, enabledOptionals = {}) {
|
|
266
305
|
const home = require("os").homedir();
|
|
267
306
|
const nexoCode = nexoHome;
|
|
268
307
|
const logsDir = path.join(nexoHome, "logs");
|
|
@@ -298,6 +337,8 @@ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAge
|
|
|
298
337
|
for (const proc of ALL_PROCESSES) {
|
|
299
338
|
// Skip macOnly processes on Linux
|
|
300
339
|
if (proc.macOnly && platform !== "darwin") continue;
|
|
340
|
+
// Skip optional processes that weren't enabled
|
|
341
|
+
if (proc.optional && !enabledOptionals[proc.optional]) continue;
|
|
301
342
|
|
|
302
343
|
const plistName = `com.nexo.${proc.name}.plist`;
|
|
303
344
|
const plistPath = path.join(launchAgentsDir, plistName);
|
|
@@ -404,6 +445,7 @@ function installAllProcesses(platform, pythonPath, nexoHome, schedule, launchAge
|
|
|
404
445
|
|
|
405
446
|
for (const proc of ALL_PROCESSES) {
|
|
406
447
|
if (proc.macOnly) continue; // tcc-approve is macOS only
|
|
448
|
+
if (proc.optional && !enabledOptionals[proc.optional]) continue;
|
|
407
449
|
const serviceName = `nexo-${proc.name}`;
|
|
408
450
|
const serviceFile = path.join(systemdDir, `${serviceName}.service`);
|
|
409
451
|
const timerFile = path.join(systemdDir, `${serviceName}.timer`);
|
|
@@ -481,6 +523,7 @@ WantedBy=timers.target
|
|
|
481
523
|
const envLine2 = `NEXO_CODE=${nexoCode}`;
|
|
482
524
|
|
|
483
525
|
for (const proc of ALL_PROCESSES) {
|
|
526
|
+
if (proc.optional && !enabledOptionals[proc.optional]) continue;
|
|
484
527
|
const sPath = scriptPath(proc);
|
|
485
528
|
const interp = interpreterPath(proc);
|
|
486
529
|
const s = getSchedule(proc);
|
|
@@ -729,8 +772,13 @@ async function main() {
|
|
|
729
772
|
// Regenerate ALL 13 LaunchAgents / systemd timers
|
|
730
773
|
const migSchedule = loadOrCreateSchedule(NEXO_HOME);
|
|
731
774
|
const migPython = findVenvPython(NEXO_HOME) || "python3";
|
|
732
|
-
|
|
733
|
-
|
|
775
|
+
let migOptionals = {};
|
|
776
|
+
try {
|
|
777
|
+
const optFile = path.join(NEXO_HOME, "config", "optionals.json");
|
|
778
|
+
if (fs.existsSync(optFile)) migOptionals = JSON.parse(fs.readFileSync(optFile, "utf8"));
|
|
779
|
+
} catch {}
|
|
780
|
+
installAllProcesses(platform, migPython, NEXO_HOME, migSchedule, LAUNCH_AGENTS, migOptionals);
|
|
781
|
+
log(" All automated processes updated.");
|
|
734
782
|
|
|
735
783
|
// Update version file
|
|
736
784
|
fs.writeFileSync(versionFile, JSON.stringify({
|
|
@@ -776,7 +824,7 @@ async function main() {
|
|
|
776
824
|
// Same version — backfill crons/ if missing (for installs before crons was shipped)
|
|
777
825
|
const cronsDest = path.join(NEXO_HOME, "crons");
|
|
778
826
|
const cronsSrc = path.join(__dirname, "..", "src", "crons");
|
|
779
|
-
if (
|
|
827
|
+
if (fs.existsSync(cronsSrc)) {
|
|
780
828
|
const copyDirRec2 = (src, dest) => {
|
|
781
829
|
fs.mkdirSync(dest, { recursive: true });
|
|
782
830
|
fs.readdirSync(src).forEach(item => {
|
|
@@ -788,7 +836,61 @@ async function main() {
|
|
|
788
836
|
});
|
|
789
837
|
};
|
|
790
838
|
copyDirRec2(cronsSrc, cronsDest);
|
|
791
|
-
log("
|
|
839
|
+
log("Refreshed crons/ directory.");
|
|
840
|
+
|
|
841
|
+
const cronSyncPath = path.join(cronsSrc, "sync.py");
|
|
842
|
+
const syncPython = findVenvPython(NEXO_HOME) || run("which python3") || "python3";
|
|
843
|
+
if (fs.existsSync(cronSyncPath)) {
|
|
844
|
+
const syncResult = spawnSync(syncPython, [cronSyncPath], {
|
|
845
|
+
env: { ...process.env, NEXO_HOME, NEXO_CODE: path.join(__dirname, "..", "src") },
|
|
846
|
+
stdio: "pipe",
|
|
847
|
+
encoding: "utf8",
|
|
848
|
+
});
|
|
849
|
+
if (syncResult.status === 0) {
|
|
850
|
+
log("Core crons reconciled with manifest.");
|
|
851
|
+
} else {
|
|
852
|
+
const syncErr = (syncResult.stderr || syncResult.stdout || "").trim();
|
|
853
|
+
log(`Cron sync warning: ${syncErr || `exit ${syncResult.status}`}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Same version — refresh packaged core skills/templates/runtime helpers too.
|
|
859
|
+
const skillsCoreDest = path.join(NEXO_HOME, "skills-core");
|
|
860
|
+
const skillsCoreSrc = path.join(__dirname, "..", "src", "skills");
|
|
861
|
+
if (fs.existsSync(skillsCoreSrc)) {
|
|
862
|
+
const copyDirRec3 = (src, dest) => {
|
|
863
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
864
|
+
fs.readdirSync(src).forEach(item => {
|
|
865
|
+
if (item === "__pycache__" || item.endsWith(".pyc")) return;
|
|
866
|
+
const srcP = path.join(src, item);
|
|
867
|
+
const destP = path.join(dest, item);
|
|
868
|
+
if (fs.statSync(srcP).isDirectory()) copyDirRec3(srcP, destP);
|
|
869
|
+
else fs.copyFileSync(srcP, destP);
|
|
870
|
+
});
|
|
871
|
+
};
|
|
872
|
+
copyDirRec3(skillsCoreSrc, skillsCoreDest);
|
|
873
|
+
log("Refreshed skills-core/ directory.");
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
["skills_runtime.py"].forEach((fname) => {
|
|
877
|
+
const srcFile = path.join(__dirname, "..", "src", fname);
|
|
878
|
+
const destFile = path.join(NEXO_HOME, fname);
|
|
879
|
+
if (fs.existsSync(srcFile)) {
|
|
880
|
+
fs.copyFileSync(srcFile, destFile);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
const templatesSrc = path.join(__dirname, "..", "templates");
|
|
885
|
+
const templatesDest = path.join(NEXO_HOME, "templates");
|
|
886
|
+
if (fs.existsSync(templatesSrc)) {
|
|
887
|
+
fs.mkdirSync(templatesDest, { recursive: true });
|
|
888
|
+
["script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"].forEach((f) => {
|
|
889
|
+
const src = path.join(templatesSrc, f);
|
|
890
|
+
if (fs.existsSync(src)) {
|
|
891
|
+
fs.copyFileSync(src, path.join(templatesDest, f));
|
|
892
|
+
}
|
|
893
|
+
});
|
|
792
894
|
}
|
|
793
895
|
|
|
794
896
|
log(`Already at v${currentVersion}. No migration needed.`);
|
|
@@ -895,6 +997,12 @@ async function main() {
|
|
|
895
997
|
caffeinateQ: " Keep Mac awake for my cognitive processes at night?\n (I consolidate memory, clean duplicates, and discover connections while you sleep)\n 1. Yes\n 2. No\n > ",
|
|
896
998
|
caffYes: "Nocturnal processes scheduled.",
|
|
897
999
|
caffNo: "Ok, I'll run them when I can.",
|
|
1000
|
+
dashboardQ: " Enable web dashboard at localhost:6174?\n (Always-on UI to explore memory, sessions, learnings, and system health)\n 1. Yes\n 2. No\n > ",
|
|
1001
|
+
dashYes: "Dashboard enabled.",
|
|
1002
|
+
dashNo: "Dashboard disabled. You can start it manually: nexo dashboard",
|
|
1003
|
+
orchestratorQ: " Enable autonomous mode? (I'll work on my own every 15 min: check followups, emails, infra, and report by email)\n 1. Yes\n 2. No\n > ",
|
|
1004
|
+
orchYes: "Autonomous mode enabled. I'll be working for you 8:00-23:00.",
|
|
1005
|
+
orchNo: "Autonomous mode disabled. I'll only work when you open a session.",
|
|
898
1006
|
autoInstallQ: " Can I install tools automatically if I need them? (brew, pip, npm)\n 1. Yes, install whatever you need\n 2. Ask me before installing anything\n > ",
|
|
899
1007
|
autoInstallYes: "Auto-install enabled.",
|
|
900
1008
|
autoInstallNo: "I'll ask before installing.",
|
|
@@ -924,6 +1032,12 @@ async function main() {
|
|
|
924
1032
|
caffeinateQ: " ¿Mantengo el Mac despierto para mis procesos cognitivos nocturnos?\n (Consolido memoria, limpio duplicados y descubro conexiones mientras duermes)\n 1. Sí\n 2. No\n > ",
|
|
925
1033
|
caffYes: "Procesos nocturnos programados.",
|
|
926
1034
|
caffNo: "Ok, los ejecutaré cuando pueda.",
|
|
1035
|
+
dashboardQ: " ¿Activar el dashboard web en localhost:6174?\n (UI siempre activa para explorar memoria, sesiones, learnings y salud del sistema)\n 1. Sí\n 2. No\n > ",
|
|
1036
|
+
dashYes: "Dashboard activado.",
|
|
1037
|
+
dashNo: "Dashboard desactivado. Puedes iniciarlo manualmente: nexo dashboard",
|
|
1038
|
+
orchestratorQ: " ¿Activar modo autónomo? (Trabajo solo cada 15 min: reviso followups, emails, infra, y te informo por email)\n 1. Sí\n 2. No\n > ",
|
|
1039
|
+
orchYes: "Modo autónomo activado. Estaré trabajando para ti de 8:00 a 23:00.",
|
|
1040
|
+
orchNo: "Modo autónomo desactivado. Solo trabajo cuando abras sesión.",
|
|
927
1041
|
autoInstallQ: " ¿Puedo instalar herramientas automáticamente si las necesito? (brew, pip, npm)\n 1. Sí, instala lo que necesites\n 2. Pregúntame antes de instalar algo\n > ",
|
|
928
1042
|
autoInstallYes: "Auto-instalación activada.",
|
|
929
1043
|
autoInstallNo: "Te preguntaré antes.",
|
|
@@ -953,6 +1067,12 @@ async function main() {
|
|
|
953
1067
|
caffeinateQ: " Garder le Mac éveillé pour mes processus nocturnes ?\n 1. Oui\n 2. Non\n > ",
|
|
954
1068
|
caffYes: "Processus nocturnes programmés.",
|
|
955
1069
|
caffNo: "Ok, je les exécuterai quand possible.",
|
|
1070
|
+
dashboardQ: " Activer le dashboard web sur localhost:6174 ?\n (UI toujours active pour explorer mémoire, sessions et santé du système)\n 1. Oui\n 2. Non\n > ",
|
|
1071
|
+
dashYes: "Dashboard activé.",
|
|
1072
|
+
dashNo: "Dashboard désactivé. Démarrage manuel : nexo dashboard",
|
|
1073
|
+
orchestratorQ: " Activer le mode autonome ? (Je travaille seul toutes les 15 min : followups, emails, infra, rapport par email)\n 1. Oui\n 2. Non\n > ",
|
|
1074
|
+
orchYes: "Mode autonome activé. Je travaille pour vous de 8h à 23h.",
|
|
1075
|
+
orchNo: "Mode autonome désactivé. Je travaille uniquement en session.",
|
|
956
1076
|
autoInstallQ: " Puis-je installer des outils automatiquement ? (brew, pip, npm)\n 1. Oui\n 2. Demande-moi avant\n > ",
|
|
957
1077
|
autoInstallYes: "Auto-installation activée.",
|
|
958
1078
|
autoInstallNo: "Je demanderai avant.",
|
|
@@ -982,6 +1102,12 @@ async function main() {
|
|
|
982
1102
|
caffeinateQ: " Mac wach halten für nächtliche Prozesse?\n 1. Ja\n 2. Nein\n > ",
|
|
983
1103
|
caffYes: "Nachtprozesse geplant.",
|
|
984
1104
|
caffNo: "Ok, führe sie aus wenn möglich.",
|
|
1105
|
+
dashboardQ: " Web-Dashboard auf localhost:6174 aktivieren?\n (Immer aktive UI für Speicher, Sitzungen und Systemgesundheit)\n 1. Ja\n 2. Nein\n > ",
|
|
1106
|
+
dashYes: "Dashboard aktiviert.",
|
|
1107
|
+
dashNo: "Dashboard deaktiviert. Manuell starten: nexo dashboard",
|
|
1108
|
+
orchestratorQ: " Autonomen Modus aktivieren? (Ich arbeite alle 15 Min selbstständig: Followups, E-Mails, Infra, Bericht per E-Mail)\n 1. Ja\n 2. Nein\n > ",
|
|
1109
|
+
orchYes: "Autonomer Modus aktiviert. Ich arbeite für dich von 8:00 bis 23:00.",
|
|
1110
|
+
orchNo: "Autonomer Modus deaktiviert. Ich arbeite nur in Sitzungen.",
|
|
985
1111
|
autoInstallQ: " Darf ich Tools automatisch installieren? (brew, pip, npm)\n 1. Ja\n 2. Frag mich vorher\n > ",
|
|
986
1112
|
autoInstallYes: "Auto-Installation aktiviert.",
|
|
987
1113
|
autoInstallNo: "Frage vorher.",
|
|
@@ -1011,6 +1137,12 @@ async function main() {
|
|
|
1011
1137
|
caffeinateQ: " Tenere il Mac sveglio per i processi notturni?\n 1. Sì\n 2. No\n > ",
|
|
1012
1138
|
caffYes: "Processi notturni programmati.",
|
|
1013
1139
|
caffNo: "Ok, li eseguirò quando possibile.",
|
|
1140
|
+
dashboardQ: " Attivare la dashboard web su localhost:6174?\n (UI sempre attiva per esplorare memoria, sessioni e salute del sistema)\n 1. Sì\n 2. No\n > ",
|
|
1141
|
+
dashYes: "Dashboard attivata.",
|
|
1142
|
+
dashNo: "Dashboard disattivata. Avvio manuale: nexo dashboard",
|
|
1143
|
+
orchestratorQ: " Attivare la modalità autonoma? (Lavoro da solo ogni 15 min: followup, email, infra, report via email)\n 1. Sì\n 2. No\n > ",
|
|
1144
|
+
orchYes: "Modalità autonoma attivata. Lavoro per te dalle 8:00 alle 23:00.",
|
|
1145
|
+
orchNo: "Modalità autonoma disattivata. Lavoro solo nelle sessioni.",
|
|
1014
1146
|
autoInstallQ: " Posso installare strumenti automaticamente? (brew, pip, npm)\n 1. Sì\n 2. Chiedimi prima\n > ",
|
|
1015
1147
|
autoInstallYes: "Auto-installazione attivata.",
|
|
1016
1148
|
autoInstallNo: "Chiederò prima.",
|
|
@@ -1040,6 +1172,12 @@ async function main() {
|
|
|
1040
1172
|
caffeinateQ: " Manter o Mac acordado para processos noturnos?\n 1. Sim\n 2. Não\n > ",
|
|
1041
1173
|
caffYes: "Processos noturnos agendados.",
|
|
1042
1174
|
caffNo: "Ok, executo quando possível.",
|
|
1175
|
+
dashboardQ: " Ativar dashboard web em localhost:6174?\n (UI sempre ativa para explorar memória, sessões e saúde do sistema)\n 1. Sim\n 2. Não\n > ",
|
|
1176
|
+
dashYes: "Dashboard ativado.",
|
|
1177
|
+
dashNo: "Dashboard desativado. Iniciar manualmente: nexo dashboard",
|
|
1178
|
+
orchestratorQ: " Ativar modo autônomo? (Trabalho sozinho a cada 15 min: followups, emails, infra, relatório por email)\n 1. Sim\n 2. Não\n > ",
|
|
1179
|
+
orchYes: "Modo autônomo ativado. Trabalho para você das 8:00 às 23:00.",
|
|
1180
|
+
orchNo: "Modo autônomo desativado. Trabalho apenas nas sessões.",
|
|
1043
1181
|
autoInstallQ: " Posso instalar ferramentas automaticamente? (brew, pip, npm)\n 1. Sim\n 2. Pergunta antes\n > ",
|
|
1044
1182
|
autoInstallYes: "Auto-instalação ativada.",
|
|
1045
1183
|
autoInstallNo: "Perguntarei antes.",
|
|
@@ -1158,6 +1296,8 @@ async function main() {
|
|
|
1158
1296
|
// Step 5: Deep scan (P9)
|
|
1159
1297
|
let doScan = false;
|
|
1160
1298
|
let doCaffeinate = false;
|
|
1299
|
+
let doDashboard = false;
|
|
1300
|
+
let doOrchestrator = false;
|
|
1161
1301
|
let autoInstall = "ask";
|
|
1162
1302
|
if (!useDefaults) {
|
|
1163
1303
|
const scanAnswer = await ask(t.scanQ);
|
|
@@ -1172,6 +1312,18 @@ async function main() {
|
|
|
1172
1312
|
console.log("");
|
|
1173
1313
|
}
|
|
1174
1314
|
|
|
1315
|
+
// Step 6b: Dashboard — always-on web UI
|
|
1316
|
+
const dashAnswer = await ask(t.dashboardQ);
|
|
1317
|
+
doDashboard = dashAnswer.trim() === "1" || dashAnswer.trim().toLowerCase().startsWith("y") || dashAnswer.trim().toLowerCase().startsWith("s");
|
|
1318
|
+
log(doDashboard ? `✓ ${t.dashYes}` : t.dashNo);
|
|
1319
|
+
console.log("");
|
|
1320
|
+
|
|
1321
|
+
// Step 6c: Day Orchestrator — autonomous NEXO cycles
|
|
1322
|
+
const orchAnswer = await ask(t.orchestratorQ);
|
|
1323
|
+
doOrchestrator = orchAnswer.trim() === "1" || orchAnswer.trim().toLowerCase().startsWith("y") || orchAnswer.trim().toLowerCase().startsWith("s");
|
|
1324
|
+
log(doOrchestrator ? `✓ ${t.orchYes}` : t.orchNo);
|
|
1325
|
+
console.log("");
|
|
1326
|
+
|
|
1175
1327
|
// Step 7: Auto-install permission (P11)
|
|
1176
1328
|
const autoInstallAnswer = await ask(t.autoInstallQ);
|
|
1177
1329
|
autoInstall = (autoInstallAnswer.trim() === "1" || autoInstallAnswer.trim().toLowerCase().startsWith("y") || autoInstallAnswer.trim().toLowerCase().startsWith("s")) ? "auto" : "ask";
|
|
@@ -1224,8 +1376,12 @@ async function main() {
|
|
|
1224
1376
|
log("Setting up NEXO home...");
|
|
1225
1377
|
const dirs = [
|
|
1226
1378
|
NEXO_HOME,
|
|
1379
|
+
path.join(NEXO_HOME, "bin"),
|
|
1227
1380
|
path.join(NEXO_HOME, "plugins"),
|
|
1228
1381
|
path.join(NEXO_HOME, "scripts"),
|
|
1382
|
+
path.join(NEXO_HOME, "skills"),
|
|
1383
|
+
path.join(NEXO_HOME, "skills-core"),
|
|
1384
|
+
path.join(NEXO_HOME, "skills-runtime"),
|
|
1229
1385
|
path.join(NEXO_HOME, "logs"),
|
|
1230
1386
|
path.join(NEXO_HOME, "backups"),
|
|
1231
1387
|
path.join(NEXO_HOME, "coordination"),
|
|
@@ -1242,7 +1398,7 @@ async function main() {
|
|
|
1242
1398
|
objective: "Improve operational excellence and reduce repeated errors",
|
|
1243
1399
|
focus_areas: ["error_prevention", "proactivity", "memory_quality"],
|
|
1244
1400
|
evolution_enabled: true,
|
|
1245
|
-
evolution_mode: "
|
|
1401
|
+
evolution_mode: "auto",
|
|
1246
1402
|
dimensions: {
|
|
1247
1403
|
episodic_memory: { current: 0, target: 90 },
|
|
1248
1404
|
autonomy: { current: 0, target: 80 },
|
|
@@ -1275,6 +1431,7 @@ async function main() {
|
|
|
1275
1431
|
const srcDir = path.join(__dirname, "..", "src");
|
|
1276
1432
|
const pluginsSrcDir = path.join(srcDir, "plugins");
|
|
1277
1433
|
const scriptsSrcDir = path.join(srcDir, "scripts");
|
|
1434
|
+
const skillsSrcDir = path.join(srcDir, "skills");
|
|
1278
1435
|
const templateDir = path.join(__dirname, "..", "templates");
|
|
1279
1436
|
|
|
1280
1437
|
// Recursive copy helper (skips __pycache__, .pyc, .db files)
|
|
@@ -1315,6 +1472,9 @@ async function main() {
|
|
|
1315
1472
|
"tools_task_history.py",
|
|
1316
1473
|
"tools_menu.py",
|
|
1317
1474
|
"requirements.txt",
|
|
1475
|
+
"cli.py",
|
|
1476
|
+
"script_registry.py",
|
|
1477
|
+
"skills_runtime.py",
|
|
1318
1478
|
];
|
|
1319
1479
|
coreFiles.forEach((f) => {
|
|
1320
1480
|
const src = path.join(srcDir, f);
|
|
@@ -1323,8 +1483,31 @@ async function main() {
|
|
|
1323
1483
|
}
|
|
1324
1484
|
});
|
|
1325
1485
|
|
|
1486
|
+
// Runtime CLI wrapper lives in NEXO_HOME/bin so it survives npx installs.
|
|
1487
|
+
const runtimeCli = [
|
|
1488
|
+
"#!/usr/bin/env bash",
|
|
1489
|
+
"set -euo pipefail",
|
|
1490
|
+
"",
|
|
1491
|
+
`NEXO_HOME="${NEXO_HOME}"`,
|
|
1492
|
+
'PYTHON="$NEXO_HOME/.venv/bin/python3"',
|
|
1493
|
+
'if [ ! -x "$PYTHON" ]; then',
|
|
1494
|
+
' if command -v python3 >/dev/null 2>&1; then',
|
|
1495
|
+
' PYTHON="python3"',
|
|
1496
|
+
" else",
|
|
1497
|
+
' PYTHON="python"',
|
|
1498
|
+
" fi",
|
|
1499
|
+
"fi",
|
|
1500
|
+
'export NEXO_HOME',
|
|
1501
|
+
'export NEXO_CODE="$NEXO_HOME"',
|
|
1502
|
+
'exec "$PYTHON" "$NEXO_HOME/cli.py" "$@"',
|
|
1503
|
+
"",
|
|
1504
|
+
].join("\n");
|
|
1505
|
+
const runtimeCliPath = path.join(NEXO_HOME, "bin", "nexo");
|
|
1506
|
+
fs.writeFileSync(runtimeCliPath, runtimeCli);
|
|
1507
|
+
fs.chmodSync(runtimeCliPath, 0o755);
|
|
1508
|
+
|
|
1326
1509
|
// Core packages (directories with __init__.py)
|
|
1327
|
-
["db", "cognitive"].forEach(pkg => {
|
|
1510
|
+
["db", "cognitive", "doctor"].forEach(pkg => {
|
|
1328
1511
|
const pkgSrc = path.join(srcDir, pkg);
|
|
1329
1512
|
if (fs.existsSync(pkgSrc)) {
|
|
1330
1513
|
copyDirRecursive(pkgSrc, path.join(NEXO_HOME, pkg));
|
|
@@ -1347,6 +1530,13 @@ async function main() {
|
|
|
1347
1530
|
fs.readdirSync(scriptsDest).filter(f => f.endsWith(".sh")).forEach(f => {
|
|
1348
1531
|
fs.chmodSync(path.join(scriptsDest, f), "755");
|
|
1349
1532
|
});
|
|
1533
|
+
syncWatchdogHashRegistry(NEXO_HOME);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Core skills are shipped separately from personal skills.
|
|
1537
|
+
if (fs.existsSync(skillsSrcDir)) {
|
|
1538
|
+
copyDirRecursive(skillsSrcDir, path.join(NEXO_HOME, "skills-core"));
|
|
1539
|
+
log(" Core skills installed.");
|
|
1350
1540
|
}
|
|
1351
1541
|
|
|
1352
1542
|
// Dashboard (recursive — includes static/, templates/)
|
|
@@ -1370,6 +1560,19 @@ async function main() {
|
|
|
1370
1560
|
log(" Crons installed.");
|
|
1371
1561
|
}
|
|
1372
1562
|
|
|
1563
|
+
// Templates directory (scripts + skills scaffolds)
|
|
1564
|
+
const templatesDest = path.join(NEXO_HOME, "templates");
|
|
1565
|
+
fs.mkdirSync(templatesDest, { recursive: true });
|
|
1566
|
+
if (fs.existsSync(templateDir)) {
|
|
1567
|
+
["script-template.py", "nexo_helper.py", "skill-template.md", "skill-script-template.py"].forEach(f => {
|
|
1568
|
+
const src = path.join(templateDir, f);
|
|
1569
|
+
if (fs.existsSync(src)) {
|
|
1570
|
+
fs.copyFileSync(src, path.join(templatesDest, f));
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
log(" Script and skill templates installed.");
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1373
1576
|
// Hooks directory
|
|
1374
1577
|
const hooksSrcDir = path.join(srcDir, "hooks");
|
|
1375
1578
|
if (fs.existsSync(hooksSrcDir)) {
|
|
@@ -1862,12 +2065,25 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
1862
2065
|
// Step 7: Create schedule.json (only on fresh install) and install ALL 13 processes
|
|
1863
2066
|
log("Setting up automated processes...");
|
|
1864
2067
|
const schedule = loadOrCreateSchedule(NEXO_HOME);
|
|
1865
|
-
|
|
2068
|
+
const enabledOptionals = { dashboard: doDashboard, orchestrator: doOrchestrator };
|
|
2069
|
+
if (isEphemeralInstall(NEXO_HOME)) {
|
|
2070
|
+
log("Ephemeral HOME/NEXO_HOME detected — skipping LaunchAgents installation.");
|
|
2071
|
+
} else {
|
|
2072
|
+
installAllProcesses(platform, python, NEXO_HOME, schedule, LAUNCH_AGENTS, enabledOptionals);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// Persist optional process preferences for auto-update
|
|
2076
|
+
try {
|
|
2077
|
+
const configDir = path.join(NEXO_HOME, "config");
|
|
2078
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
2079
|
+
const optFile = path.join(configDir, "optionals.json");
|
|
2080
|
+
fs.writeFileSync(optFile, JSON.stringify(enabledOptionals, null, 2));
|
|
2081
|
+
} catch {}
|
|
1866
2082
|
|
|
1867
2083
|
// Note: prevent-sleep and tcc-approve are now part of ALL_PROCESSES
|
|
1868
2084
|
// and installed by installAllProcesses() above. No separate caffeinate block needed.
|
|
1869
2085
|
|
|
1870
|
-
// Step 8: Create shell alias
|
|
2086
|
+
// Step 8: Create shell alias and add runtime CLI to PATH
|
|
1871
2087
|
log("Creating shell alias...");
|
|
1872
2088
|
const aliasName = operatorName.toLowerCase();
|
|
1873
2089
|
const savedCliPath = (() => {
|
|
@@ -1877,6 +2093,8 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
1877
2093
|
const claudeBin = savedCliPath || run("which claude") || "claude";
|
|
1878
2094
|
const aliasLine = `alias ${aliasName}='${claudeBin} --dangerously-skip-permissions "."'`;
|
|
1879
2095
|
const aliasComment = `# ${operatorName} — start Claude Code with ${operatorName} speaking first`;
|
|
2096
|
+
const nexoPathLine = `export PATH="${path.join(NEXO_HOME, "bin")}:$PATH"`;
|
|
2097
|
+
const nexoPathComment = "# NEXO runtime CLI";
|
|
1880
2098
|
|
|
1881
2099
|
// Detect shell and add alias
|
|
1882
2100
|
const userShell = process.env.SHELL || "/bin/bash";
|
|
@@ -1899,6 +2117,14 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
1899
2117
|
rcContent = fs.readFileSync(rcFile, "utf8");
|
|
1900
2118
|
}
|
|
1901
2119
|
|
|
2120
|
+
if (!rcContent.includes(nexoPathLine)) {
|
|
2121
|
+
fs.appendFileSync(rcFile, `\n${nexoPathComment}\n${nexoPathLine}\n`);
|
|
2122
|
+
log(`Added NEXO runtime CLI to ${path.basename(rcFile)}`);
|
|
2123
|
+
rcContent += `\n${nexoPathComment}\n${nexoPathLine}\n`;
|
|
2124
|
+
} else {
|
|
2125
|
+
log(`Runtime CLI already present in ${path.basename(rcFile)}`);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
1902
2128
|
if (!rcContent.includes(`alias ${aliasName}=`)) {
|
|
1903
2129
|
fs.appendFileSync(rcFile, `\n${aliasComment}\n${aliasLine}\n`);
|
|
1904
2130
|
log(`Added '${aliasName}' alias to ${path.basename(rcFile)}`);
|
|
@@ -1906,7 +2132,7 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
1906
2132
|
log(`Alias '${aliasName}' already exists in ${path.basename(rcFile)}`);
|
|
1907
2133
|
}
|
|
1908
2134
|
}
|
|
1909
|
-
log(`After setup, open a new terminal and type: ${aliasName}`);
|
|
2135
|
+
log(`After setup, open a new terminal and type: ${aliasName} or nexo`);
|
|
1910
2136
|
console.log("");
|
|
1911
2137
|
|
|
1912
2138
|
// Step 9: Generate CLAUDE.md template
|
package/bin/nexo.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* nexo — Runtime operational CLI for NEXO.
|
|
4
|
+
*
|
|
5
|
+
* Thin Node launcher that resolves NEXO_HOME, finds Python,
|
|
6
|
+
* and delegates to src/cli.py (repo mode) or NEXO_HOME/cli.py (installed mode).
|
|
7
|
+
*
|
|
8
|
+
* Business logic lives in Python, not here.
|
|
9
|
+
*/
|
|
10
|
+
const { spawnSync } = require("child_process");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const os = require("os");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
const NEXO_HOME = process.env.NEXO_HOME || path.join(os.homedir(), ".nexo");
|
|
16
|
+
|
|
17
|
+
function findPython() {
|
|
18
|
+
const candidates = [
|
|
19
|
+
path.join(NEXO_HOME, ".venv", "bin", "python3"),
|
|
20
|
+
path.join(NEXO_HOME, ".venv", "bin", "python"),
|
|
21
|
+
"python3",
|
|
22
|
+
"python",
|
|
23
|
+
];
|
|
24
|
+
for (const c of candidates) {
|
|
25
|
+
if (c.includes("/") ? fs.existsSync(c) : true) return c;
|
|
26
|
+
}
|
|
27
|
+
return "python3";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findCliPy() {
|
|
31
|
+
const repoCandidate = path.join(__dirname, "..", "src", "cli.py");
|
|
32
|
+
const installedCandidate = path.join(NEXO_HOME, "cli.py");
|
|
33
|
+
if (fs.existsSync(repoCandidate)) return repoCandidate;
|
|
34
|
+
return installedCandidate;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const python = findPython();
|
|
38
|
+
const cliPy = findCliPy();
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(cliPy)) {
|
|
41
|
+
console.error(`NEXO CLI not found at ${cliPy}`);
|
|
42
|
+
console.error("Run 'nexo-brain' first to complete installation.");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = spawnSync(python, [cliPy, ...process.argv.slice(2)], {
|
|
47
|
+
stdio: "inherit",
|
|
48
|
+
env: {
|
|
49
|
+
...process.env,
|
|
50
|
+
NEXO_HOME,
|
|
51
|
+
NEXO_CODE: path.join(__dirname, "..", "src"),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|