nexo-brain 6.5.0 → 7.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.
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +6 -2
- package/bin/nexo-brain.js +310 -44
- package/bin/nexo.js +6 -0
- package/package.json +1 -1
- package/src/agent_runner.py +3 -2
- package/src/auto_close_sessions.py +9 -2
- package/src/auto_update.py +1216 -43
- package/src/automation_controls.py +814 -0
- package/src/bootstrap_docs.py +2 -1
- package/src/calibration_migration.py +5 -2
- package/src/classifier_local.py +43 -1
- package/src/cli.py +201 -22
- package/src/cli_email.py +213 -20
- package/src/client_preferences.py +7 -4
- package/src/client_sync.py +188 -115
- package/src/cron_recovery.py +16 -5
- package/src/crons/manifest.json +39 -0
- package/src/crons/sync.py +101 -20
- package/src/dashboard/app.py +9 -14
- package/src/db/__init__.py +1 -0
- package/src/db/_core.py +22 -9
- package/src/db/_email_accounts.py +93 -7
- package/src/db/_personal_scripts.py +122 -24
- package/src/db/_schema.py +73 -0
- package/src/db/_skills.py +145 -2
- package/src/desktop_bridge.py +33 -7
- package/src/doctor/providers/boot.py +21 -7
- package/src/doctor/providers/deep.py +6 -5
- package/src/doctor/providers/runtime.py +19 -18
- package/src/email_config.py +144 -33
- package/src/enforcement_engine.py +126 -5
- package/src/evolution_cycle.py +7 -6
- package/src/guardian_config.py +3 -3
- package/src/guardian_runtime_surfaces.py +248 -0
- package/src/guardian_telemetry.py +4 -1
- package/src/health_check.py +4 -2
- package/src/hook_guardrails.py +2 -0
- package/src/hooks/auto_capture.py +7 -2
- package/src/hooks/capture-session.sh +6 -2
- package/src/hooks/session-start.sh +41 -11
- package/src/hooks/session_start.py +5 -1
- package/src/paths.py +467 -0
- package/src/plugins/personal_plugins.py +14 -7
- package/src/plugins/personal_scripts.py +4 -1
- package/src/plugins/recover.py +3 -2
- package/src/plugins/schedule.py +4 -3
- package/src/plugins/update.py +68 -14
- package/src/presets/guardian_default.json +2 -2
- package/src/public_contribution.py +12 -7
- package/src/resonance_map.py +2 -0
- package/src/runtime_power.py +7 -6
- package/src/script_registry.py +469 -59
- package/src/scripts/backfill_task_owner.py +263 -0
- package/src/scripts/deep-sleep/apply_findings.py +70 -7
- package/src/scripts/deep-sleep/collect.py +73 -11
- package/src/scripts/deep-sleep/extract.py +19 -7
- package/src/scripts/deep-sleep/synthesize.py +3 -1
- package/src/scripts/nexo-auto-update.py +3 -2
- package/src/scripts/nexo-backup.sh +6 -3
- package/src/scripts/nexo-catchup.py +7 -6
- package/src/scripts/nexo-cognitive-decay.py +4 -3
- package/src/scripts/nexo-cortex-cycle.py +3 -2
- package/src/scripts/nexo-cron-wrapper.sh +6 -3
- package/src/scripts/nexo-daily-self-audit.py +55 -23
- package/src/scripts/nexo-deep-sleep.sh +6 -3
- package/src/scripts/nexo-email-migrate-config.py +111 -25
- package/src/scripts/nexo-email-monitor.py +2180 -0
- package/src/scripts/nexo-evolution-run.py +20 -17
- package/src/scripts/nexo-followup-hygiene.py +4 -3
- package/src/scripts/nexo-followup-runner.py +921 -0
- package/src/scripts/nexo-immune.py +7 -6
- package/src/scripts/nexo-impact-scorer.py +3 -2
- package/src/scripts/nexo-inbox-hook.sh +1 -1
- package/src/scripts/nexo-learning-housekeep.py +3 -2
- package/src/scripts/nexo-learning-validator.py +4 -3
- package/src/scripts/nexo-morning-agent.py +433 -0
- package/src/scripts/nexo-outcome-checker.py +3 -2
- package/src/scripts/nexo-postmortem-consolidator.py +10 -9
- package/src/scripts/nexo-pre-commit.py +2 -1
- package/src/scripts/nexo-proactive-dashboard.py +6 -4
- package/src/scripts/nexo-runtime-preflight.py +5 -4
- package/src/scripts/nexo-send-reply.py +483 -0
- package/src/scripts/nexo-sleep.py +9 -8
- package/src/scripts/nexo-snapshot-restore.sh +1 -1
- package/src/scripts/nexo-synthesis.py +7 -6
- package/src/scripts/nexo-tcc-approve.sh +2 -2
- package/src/scripts/nexo-watchdog-smoke.py +7 -5
- package/src/scripts/nexo-watchdog.sh +37 -26
- package/src/scripts/phase_guardian_analysis.py +3 -2
- package/src/state_watchers_runtime.py +6 -4
- package/src/system_catalog.py +23 -21
- package/src/tools_sessions.py +2 -1
- package/src/user_context.py +11 -5
- package/src/user_data_portability.py +26 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.1.0",
|
|
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",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"hooks": "./hooks/hooks.json",
|
|
27
27
|
"userConfig": {
|
|
28
28
|
"operator_name": {
|
|
29
|
-
"description": "What should your AI co-operator call itself? (default:
|
|
29
|
+
"description": "What should your AI co-operator call itself? (default: Nova)",
|
|
30
30
|
"sensitive": false
|
|
31
31
|
}
|
|
32
32
|
}
|
package/README.md
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
[Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
|
|
20
20
|
|
|
21
|
-
Version `
|
|
21
|
+
Version `7.1.0` is the current packaged-runtime line. It closes the post-F0.6 runtime contract: `~/.nexo/core` is now the canonical shipped code root, layout healers re-run automatically after sync/update, Desktop consumes a Brain-generated Guardian runtime snapshot instead of stale manual lists, the core automation surface now officially includes `email-monitor`, `followup-runner`, and `morning-agent`, and the local classifier baseline auto-installs on fresh installs / `nexo update` unless the operator explicitly opts out with `NEXO_LOCAL_CLASSIFIER=off`. The companion NEXO Desktop client (v0.22.0, closed-source distributed separately) turns that same contract into a closed bootstrap/login/product flow.
|
|
22
|
+
|
|
23
|
+
Previously in `7.0.1`: hotfix over v7.0.0 (db._core.DB_PATH was only caller still hardcoded to legacy ~/.nexo/data/nexo.db; every shared-DB command silently returned empty results post-migration). Previously in `7.0.0`: **BREAKING — Plan Consolidado fase F0.6**: physical separation of the runtime tree into `~/.nexo/{core,personal,runtime}/`. The flat layout (`~/.nexo/scripts/`, `brain/`, `data/`, `operations/`, ...) is gone. Operators on v6.x are auto-migrated on first `nexo update`; fresh installs land directly in the new tree. New `paths.py` helpers are transition-aware.
|
|
24
|
+
|
|
25
|
+
Previously in `6.5.0`: Plan Consolidado fase F0.2: operators can now `nexo scripts enable|disable|status <name>` any personal automation. The cron wrapper honours the flag at every tick (`exit 0` with `summary='[disabled]'` while the LaunchAgent stays loaded). The companion NEXO Desktop client (a closed-source product, distributed separately) wires the same toggle into its Automatizaciones panel. See [CHANGELOG](CHANGELOG.md) for the full diff.
|
|
22
26
|
|
|
23
27
|
> **About NEXO Desktop.** NEXO Desktop is a separate closed-source companion app distributed at [systeam.es/nexo-desktop](https://systeam.es/nexo-desktop) — its source does not live in this repo. When release notes mention Desktop they describe a coordinated client release that consumes the Brain's CLI / MCP contract; the Brain itself is fully usable on its own (terminal, Codex, Claude Code, or any MCP client).
|
|
24
28
|
|
|
@@ -792,7 +796,7 @@ npx nexo-brain
|
|
|
792
796
|
The installer handles everything and syncs the same `nexo` MCP brain into Claude Code, Claude Desktop, and Codex when those clients are present:
|
|
793
797
|
|
|
794
798
|
```
|
|
795
|
-
How should I call myself? (default:
|
|
799
|
+
How should I call myself? (default: Nova) > Atlas
|
|
796
800
|
|
|
797
801
|
Can I explore your workspace to learn about your projects? (y/n) > y
|
|
798
802
|
|
package/bin/nexo-brain.js
CHANGED
|
@@ -21,6 +21,23 @@ const path = require("path");
|
|
|
21
21
|
const readline = require("readline");
|
|
22
22
|
|
|
23
23
|
let NEXO_HOME = process.env.NEXO_HOME || path.join(require("os").homedir(), ".nexo");
|
|
24
|
+
const DEFAULT_ASSISTANT_NAME = "Nova";
|
|
25
|
+
const RESERVED_ASSISTANT_NAME_KEYS = new Set(["nexo", "nexobrain", "nexodesktop"]);
|
|
26
|
+
|
|
27
|
+
function normalizeAssistantNameCandidate(value) {
|
|
28
|
+
return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isReservedAssistantName(value) {
|
|
32
|
+
const normalized = normalizeAssistantNameCandidate(value);
|
|
33
|
+
if (!normalized) return false;
|
|
34
|
+
for (const reserved of RESERVED_ASSISTANT_NAME_KEYS) {
|
|
35
|
+
if (normalized === reserved || normalized.includes(reserved)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
24
41
|
|
|
25
42
|
function shouldSkipShellProfileBackfill() {
|
|
26
43
|
// Mirror of _should_skip_shell_profile_backfill() in src/auto_update.py.
|
|
@@ -197,10 +214,11 @@ function isDuplicateArtifactName(name, dirPath = "") {
|
|
|
197
214
|
|
|
198
215
|
function syncWatchdogHashRegistry(nexoHome) {
|
|
199
216
|
try {
|
|
200
|
-
const
|
|
217
|
+
const scriptsDir = runtimeScriptsDir(nexoHome);
|
|
218
|
+
const watchdogPath = path.join(scriptsDir, "nexo-watchdog.sh");
|
|
201
219
|
if (!fs.existsSync(watchdogPath)) return;
|
|
202
220
|
|
|
203
|
-
const registryPath = path.join(
|
|
221
|
+
const registryPath = path.join(scriptsDir, ".watchdog-hashes");
|
|
204
222
|
const entries = new Map();
|
|
205
223
|
if (fs.existsSync(registryPath)) {
|
|
206
224
|
for (const line of fs.readFileSync(registryPath, "utf8").split(/\r?\n/)) {
|
|
@@ -222,6 +240,40 @@ function syncWatchdogHashRegistry(nexoHome) {
|
|
|
222
240
|
}
|
|
223
241
|
}
|
|
224
242
|
|
|
243
|
+
function runtimeCodeDir(nexoHome) {
|
|
244
|
+
const coreDir = path.join(nexoHome, "core");
|
|
245
|
+
for (const candidate of [coreDir, nexoHome]) {
|
|
246
|
+
if (
|
|
247
|
+
fs.existsSync(path.join(candidate, "cli.py")) ||
|
|
248
|
+
fs.existsSync(path.join(candidate, "server.py")) ||
|
|
249
|
+
fs.existsSync(path.join(candidate, "db"))
|
|
250
|
+
) {
|
|
251
|
+
return candidate;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return fs.existsSync(coreDir) ? coreDir : nexoHome;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function runtimeServerPath(nexoHome) {
|
|
258
|
+
const codeDir = runtimeCodeDir(nexoHome);
|
|
259
|
+
if (fs.existsSync(path.join(codeDir, "server.py"))) {
|
|
260
|
+
return path.join(codeDir, "server.py");
|
|
261
|
+
}
|
|
262
|
+
return path.join(nexoHome, "server.py");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function runtimeHooksDir(nexoHome) {
|
|
266
|
+
const codeDir = runtimeCodeDir(nexoHome);
|
|
267
|
+
const hooksDir = path.join(codeDir, "hooks");
|
|
268
|
+
return fs.existsSync(hooksDir) ? hooksDir : path.join(nexoHome, "hooks");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function runtimeScriptsDir(nexoHome) {
|
|
272
|
+
const codeDir = runtimeCodeDir(nexoHome);
|
|
273
|
+
const scriptsDir = path.join(codeDir, "scripts");
|
|
274
|
+
return fs.existsSync(scriptsDir) ? scriptsDir : path.join(nexoHome, "scripts");
|
|
275
|
+
}
|
|
276
|
+
|
|
225
277
|
function writeRuntimeCoreArtifactsManifest(nexoHome, srcDir) {
|
|
226
278
|
try {
|
|
227
279
|
const listTopLevelFiles = (dirPath) => {
|
|
@@ -233,17 +285,23 @@ function writeRuntimeCoreArtifactsManifest(nexoHome, srcDir) {
|
|
|
233
285
|
})
|
|
234
286
|
.sort();
|
|
235
287
|
};
|
|
236
|
-
const configDir = path.join(nexoHome, "config");
|
|
237
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
238
288
|
const payload = {
|
|
239
289
|
generated_at: new Date().toISOString(),
|
|
240
290
|
script_names: listTopLevelFiles(path.join(srcDir, "scripts")),
|
|
241
291
|
hook_names: listTopLevelFiles(path.join(srcDir, "hooks")),
|
|
242
292
|
};
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
293
|
+
const manifestBody = `${JSON.stringify(payload, null, 2)}\n`;
|
|
294
|
+
const configDirs = [
|
|
295
|
+
path.join(nexoHome, "config"),
|
|
296
|
+
path.join(nexoHome, "personal", "config"),
|
|
297
|
+
];
|
|
298
|
+
const seen = new Set();
|
|
299
|
+
for (const configDir of configDirs) {
|
|
300
|
+
if (seen.has(configDir)) continue;
|
|
301
|
+
seen.add(configDir);
|
|
302
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
303
|
+
fs.writeFileSync(path.join(configDir, "runtime-core-artifacts.json"), manifestBody);
|
|
304
|
+
}
|
|
247
305
|
} catch (err) {
|
|
248
306
|
log(`WARN: could not write runtime core-artifacts manifest: ${err.message}`);
|
|
249
307
|
}
|
|
@@ -260,6 +318,44 @@ function syncRuntimePackageMetadata(repoRoot = path.join(__dirname, ".."), runti
|
|
|
260
318
|
}
|
|
261
319
|
}
|
|
262
320
|
|
|
321
|
+
function finalizeF06Layout(python, nexoHome = NEXO_HOME) {
|
|
322
|
+
try {
|
|
323
|
+
const result = spawnSync(
|
|
324
|
+
python,
|
|
325
|
+
[
|
|
326
|
+
"-c",
|
|
327
|
+
[
|
|
328
|
+
"import auto_update",
|
|
329
|
+
"auto_update._maybe_migrate_to_f06_layout()",
|
|
330
|
+
"auto_update._ensure_f06_legacy_shims()",
|
|
331
|
+
"auto_update._rewrite_f06_launch_agents()",
|
|
332
|
+
].join("; "),
|
|
333
|
+
],
|
|
334
|
+
{
|
|
335
|
+
cwd: nexoHome,
|
|
336
|
+
env: {
|
|
337
|
+
...process.env,
|
|
338
|
+
NEXO_HOME: nexoHome,
|
|
339
|
+
NEXO_CODE: runtimeCodeDir(nexoHome),
|
|
340
|
+
PYTHONPATH: nexoHome,
|
|
341
|
+
},
|
|
342
|
+
encoding: "utf8",
|
|
343
|
+
},
|
|
344
|
+
);
|
|
345
|
+
if (result.status !== 0) {
|
|
346
|
+
const detail = (result.stderr || result.stdout || "").trim();
|
|
347
|
+
throw new Error(detail || "unknown error");
|
|
348
|
+
}
|
|
349
|
+
const marker = path.join(nexoHome, ".structure-version");
|
|
350
|
+
if (!fs.existsSync(marker) || fs.readFileSync(marker, "utf8").trim() !== "F0.6") {
|
|
351
|
+
throw new Error("F0.6 structure marker missing after layout finalization");
|
|
352
|
+
}
|
|
353
|
+
return { ok: true };
|
|
354
|
+
} catch (err) {
|
|
355
|
+
return { ok: false, error: String((err && err.message) || err) };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
263
359
|
function getCoreRuntimeFlatFiles(srcDir = path.join(__dirname, "..", "src")) {
|
|
264
360
|
const staticFiles = [
|
|
265
361
|
"server.py",
|
|
@@ -648,8 +744,9 @@ function _hookCommand(hook, hooksDir, nexoHome) {
|
|
|
648
744
|
}
|
|
649
745
|
|
|
650
746
|
function _writeHooksStatus(nexoHome, manifestEntries, registrations) {
|
|
651
|
-
// Publish
|
|
652
|
-
//
|
|
747
|
+
// Publish the canonical hook-health contract under runtime/operations/.
|
|
748
|
+
// Keep ~/.nexo/hooks_status.json only as a legacy alias so the root tree
|
|
749
|
+
// does not remain the live source of truth.
|
|
653
750
|
try {
|
|
654
751
|
const now = new Date();
|
|
655
752
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
@@ -664,19 +761,34 @@ function _writeHooksStatus(nexoHome, manifestEntries, registrations) {
|
|
|
664
761
|
healthy,
|
|
665
762
|
hooks: registrations,
|
|
666
763
|
};
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
764
|
+
const body = JSON.stringify(payload, null, 2) + "\n";
|
|
765
|
+
const canonicalDir = path.join(nexoHome, "runtime", "operations");
|
|
766
|
+
const canonicalPath = path.join(canonicalDir, "hooks_status.json");
|
|
767
|
+
const legacyPath = path.join(nexoHome, "hooks_status.json");
|
|
768
|
+
|
|
769
|
+
fs.mkdirSync(canonicalDir, { recursive: true });
|
|
770
|
+
fs.writeFileSync(canonicalPath, body);
|
|
771
|
+
|
|
772
|
+
try {
|
|
773
|
+
if (fs.existsSync(legacyPath) || fs.lstatSync(legacyPath).isSymbolicLink()) {
|
|
774
|
+
fs.rmSync(legacyPath, { force: true });
|
|
775
|
+
}
|
|
776
|
+
} catch (_) {}
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
const relTarget = path.relative(path.dirname(legacyPath), canonicalPath) || path.basename(canonicalPath);
|
|
780
|
+
fs.symlinkSync(relTarget, legacyPath);
|
|
781
|
+
} catch (_) {
|
|
782
|
+
fs.writeFileSync(legacyPath, body);
|
|
783
|
+
}
|
|
672
784
|
} catch (_) {}
|
|
673
785
|
}
|
|
674
786
|
|
|
675
787
|
/**
|
|
676
788
|
* Register every hook declared by src/hooks/manifest.json into the
|
|
677
789
|
* Claude Code settings file. Idempotent, never removes user-owned hooks.
|
|
678
|
-
* Writes
|
|
679
|
-
* display hook health without parsing settings.json.
|
|
790
|
+
* Writes the canonical hook-status contract after each run so NEXO Desktop
|
|
791
|
+
* can display hook health without parsing settings.json.
|
|
680
792
|
*/
|
|
681
793
|
function registerAllCoreHooks(settings, hooksDir, nexoHome) {
|
|
682
794
|
if (!settings.hooks) settings.hooks = {};
|
|
@@ -904,7 +1016,9 @@ function detectInstalledClients() {
|
|
|
904
1016
|
? [path.join(homeDir, "Applications", "Claude.app"), "/Applications/Claude.app"]
|
|
905
1017
|
: [];
|
|
906
1018
|
const desktopAppPath = desktopApps.find((candidate) => fs.existsSync(candidate)) || "";
|
|
907
|
-
const
|
|
1019
|
+
const managedClaudeBin = resolveManagedClaudeBinary();
|
|
1020
|
+
const persistedClaudeBin = readPersistedClaudeCliPath();
|
|
1021
|
+
const claudeBin = managedClaudeBin || persistedClaudeBin || run("which claude", { env: buildManagedCliEnv() }) || run("which claude") || "";
|
|
908
1022
|
const codexBin = run("which codex") || "";
|
|
909
1023
|
return {
|
|
910
1024
|
claude_code: {
|
|
@@ -925,6 +1039,64 @@ function detectInstalledClients() {
|
|
|
925
1039
|
};
|
|
926
1040
|
}
|
|
927
1041
|
|
|
1042
|
+
function managedClaudePrefix() {
|
|
1043
|
+
const explicit = String(process.env.NEXO_CLAUDE_PREFIX || "").trim();
|
|
1044
|
+
if (explicit) return explicit;
|
|
1045
|
+
return path.join(NEXO_HOME, "runtime", "bootstrap", "npm-global");
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function buildManagedCliEnv(extraEnv = {}) {
|
|
1049
|
+
const prefix = managedClaudePrefix();
|
|
1050
|
+
const parts = [
|
|
1051
|
+
path.join(prefix, "bin"),
|
|
1052
|
+
process.env.PATH || "",
|
|
1053
|
+
].filter(Boolean);
|
|
1054
|
+
return {
|
|
1055
|
+
...process.env,
|
|
1056
|
+
npm_config_prefix: prefix,
|
|
1057
|
+
PATH: parts.join(path.delimiter),
|
|
1058
|
+
...extraEnv,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function resolveManagedClaudeBinary() {
|
|
1063
|
+
const prefix = managedClaudePrefix();
|
|
1064
|
+
const candidates = process.platform === "win32"
|
|
1065
|
+
? [path.join(prefix, "claude.cmd"), path.join(prefix, "bin", "claude.cmd")]
|
|
1066
|
+
: [path.join(prefix, "bin", "claude"), path.join(prefix, "claude")];
|
|
1067
|
+
return candidates.find((candidate) => candidate && fs.existsSync(candidate)) || "";
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function readPersistedClaudeCliPath() {
|
|
1071
|
+
const candidates = [
|
|
1072
|
+
path.join(NEXO_HOME, "config", "claude-cli-path"),
|
|
1073
|
+
path.join(NEXO_HOME, "personal", "config", "claude-cli-path"),
|
|
1074
|
+
];
|
|
1075
|
+
for (const file of candidates) {
|
|
1076
|
+
try {
|
|
1077
|
+
if (!fs.existsSync(file)) continue;
|
|
1078
|
+
const value = String(fs.readFileSync(file, "utf8") || "").trim();
|
|
1079
|
+
if (value && fs.existsSync(value)) return value;
|
|
1080
|
+
} catch {}
|
|
1081
|
+
}
|
|
1082
|
+
return "";
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function persistClaudeCliPath(claudePath) {
|
|
1086
|
+
const value = String(claudePath || "").trim();
|
|
1087
|
+
if (!value) return;
|
|
1088
|
+
const targets = [
|
|
1089
|
+
path.join(NEXO_HOME, "config", "claude-cli-path"),
|
|
1090
|
+
path.join(NEXO_HOME, "personal", "config", "claude-cli-path"),
|
|
1091
|
+
];
|
|
1092
|
+
for (const file of targets) {
|
|
1093
|
+
try {
|
|
1094
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1095
|
+
fs.writeFileSync(file, value);
|
|
1096
|
+
} catch {}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
928
1100
|
function clientSetupStrings(lang) {
|
|
929
1101
|
if (lang === "es") {
|
|
930
1102
|
return {
|
|
@@ -1092,17 +1264,48 @@ function requiredCliClients(setup) {
|
|
|
1092
1264
|
}
|
|
1093
1265
|
|
|
1094
1266
|
function installClaudeCodeCli(platform) {
|
|
1095
|
-
let claudeInstalled =
|
|
1096
|
-
if (claudeInstalled)
|
|
1267
|
+
let claudeInstalled = detectInstalledClients().claude_code.path || "";
|
|
1268
|
+
if (claudeInstalled) {
|
|
1269
|
+
persistClaudeCliPath(claudeInstalled);
|
|
1270
|
+
return { installed: true, path: claudeInstalled };
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const installEnv = buildManagedCliEnv();
|
|
1274
|
+
const desktopNode = String(process.env.NEXO_DESKTOP_NODE || "").trim();
|
|
1275
|
+
const bundledNpmCli = String(process.env.NEXO_DESKTOP_NPM_CLI || "").trim();
|
|
1276
|
+
const managedPrefix = managedClaudePrefix();
|
|
1277
|
+
|
|
1278
|
+
if (desktopNode && bundledNpmCli) {
|
|
1279
|
+
spawnSync(
|
|
1280
|
+
desktopNode,
|
|
1281
|
+
[bundledNpmCli, "install", "-g", "--prefix", managedPrefix, "@anthropic-ai/claude-code"],
|
|
1282
|
+
{
|
|
1283
|
+
stdio: "inherit",
|
|
1284
|
+
env: { ...installEnv, ELECTRON_RUN_AS_NODE: "1" },
|
|
1285
|
+
},
|
|
1286
|
+
);
|
|
1287
|
+
claudeInstalled = detectInstalledClients().claude_code.path || "";
|
|
1288
|
+
if (claudeInstalled) {
|
|
1289
|
+
persistClaudeCliPath(claudeInstalled);
|
|
1290
|
+
return { installed: true, path: claudeInstalled };
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1097
1293
|
|
|
1098
|
-
spawnSync("npx", ["-y", "@anthropic-ai/claude-code", "--version"], {
|
|
1099
|
-
|
|
1294
|
+
spawnSync("npx", ["-y", "@anthropic-ai/claude-code", "--version"], {
|
|
1295
|
+
stdio: "pipe",
|
|
1296
|
+
timeout: 60000,
|
|
1297
|
+
env: installEnv,
|
|
1298
|
+
});
|
|
1299
|
+
claudeInstalled = detectInstalledClients().claude_code.path || "";
|
|
1100
1300
|
if (!claudeInstalled) {
|
|
1101
1301
|
const npmCmd = platform === "linux" ? "sudo" : "npm";
|
|
1102
|
-
const npmArgs = platform === "linux"
|
|
1103
|
-
|
|
1104
|
-
|
|
1302
|
+
const npmArgs = platform === "linux"
|
|
1303
|
+
? ["npm", "install", "-g", "@anthropic-ai/claude-code"]
|
|
1304
|
+
: ["install", "-g", "--prefix", managedPrefix, "@anthropic-ai/claude-code"];
|
|
1305
|
+
spawnSync(npmCmd, npmArgs, { stdio: "inherit", env: installEnv });
|
|
1306
|
+
claudeInstalled = detectInstalledClients().claude_code.path || "";
|
|
1105
1307
|
}
|
|
1308
|
+
if (claudeInstalled) persistClaudeCliPath(claudeInstalled);
|
|
1106
1309
|
return { installed: Boolean(claudeInstalled), path: claudeInstalled || "" };
|
|
1107
1310
|
}
|
|
1108
1311
|
|
|
@@ -1642,6 +1845,7 @@ async function main() {
|
|
|
1642
1845
|
|| process.argv.includes("--yes")
|
|
1643
1846
|
|| process.argv.includes("--skip")
|
|
1644
1847
|
|| process.argv.includes("-y");
|
|
1848
|
+
const smokeTestMode = process.env.NEXO_TESTING_SMOKE === "1";
|
|
1645
1849
|
|
|
1646
1850
|
console.log("");
|
|
1647
1851
|
console.log(
|
|
@@ -1847,11 +2051,16 @@ async function main() {
|
|
|
1847
2051
|
migrated_from: installedVersion,
|
|
1848
2052
|
}, null, 2));
|
|
1849
2053
|
syncRuntimePackageMetadata(path.join(__dirname, ".."), NEXO_HOME);
|
|
2054
|
+
log("Finalizing F0.6 runtime layout...");
|
|
2055
|
+
const migLayoutFinalize = finalizeF06Layout(migPython, NEXO_HOME);
|
|
2056
|
+
if (!migLayoutFinalize.ok) {
|
|
2057
|
+
throw new Error(`F0.6 layout finalization failed: ${migLayoutFinalize.error}`);
|
|
2058
|
+
}
|
|
1850
2059
|
|
|
1851
2060
|
// Save updated CLAUDE.md template as reference (don't overwrite user's)
|
|
1852
2061
|
const templateSrc = path.join(__dirname, "..", "templates", "CLAUDE.md.template");
|
|
1853
2062
|
if (fs.existsSync(templateSrc)) {
|
|
1854
|
-
const operatorName = installed.operator_name ||
|
|
2063
|
+
const operatorName = installed.operator_name || DEFAULT_ASSISTANT_NAME;
|
|
1855
2064
|
let claudeMd = fs.readFileSync(templateSrc, "utf8")
|
|
1856
2065
|
.replace(/\{\{NAME\}\}/g, operatorName)
|
|
1857
2066
|
.replace(/\{\{NEXO_HOME\}\}/g, NEXO_HOME);
|
|
@@ -1874,7 +2083,7 @@ async function main() {
|
|
|
1874
2083
|
}
|
|
1875
2084
|
|
|
1876
2085
|
// Restore operator shell alias + PATH if lost during previous updates
|
|
1877
|
-
const migOperatorName = installed.operator_name ||
|
|
2086
|
+
const migOperatorName = installed.operator_name || DEFAULT_ASSISTANT_NAME;
|
|
1878
2087
|
const migAliasName = migOperatorName.toLowerCase();
|
|
1879
2088
|
if (migAliasName !== "nexo") {
|
|
1880
2089
|
const migSkip = shouldSkipShellProfileBackfill();
|
|
@@ -2003,6 +2212,11 @@ async function main() {
|
|
|
2003
2212
|
}
|
|
2004
2213
|
}
|
|
2005
2214
|
}
|
|
2215
|
+
log("Finalizing F0.6 runtime layout...");
|
|
2216
|
+
const syncLayoutFinalize = finalizeF06Layout(syncPython, NEXO_HOME);
|
|
2217
|
+
if (!syncLayoutFinalize.ok) {
|
|
2218
|
+
throw new Error(`F0.6 layout finalization failed: ${syncLayoutFinalize.error}`);
|
|
2219
|
+
}
|
|
2006
2220
|
|
|
2007
2221
|
logMacPermissionsNotice(NEXO_HOME, syncPython);
|
|
2008
2222
|
|
|
@@ -2069,8 +2283,9 @@ async function main() {
|
|
|
2069
2283
|
dataDirConfirm: (p) => `Data directory: ${p}`,
|
|
2070
2284
|
askUserName: " What's your name? > ",
|
|
2071
2285
|
userGreet: (n) => `Nice to meet you, ${n}.`,
|
|
2072
|
-
askAgentName:
|
|
2286
|
+
askAgentName: ` What should I call myself? (default: ${DEFAULT_ASSISTANT_NAME}) > `,
|
|
2073
2287
|
agentConfirm: (n) => `Got it. I'm ${n}.`,
|
|
2288
|
+
agentNameReserved: "That name is reserved for the product. Pick a different assistant name.",
|
|
2074
2289
|
calibTitle: "Let's calibrate my personality to work best with you.",
|
|
2075
2290
|
calibNote: "(You can change these anytime via nexo_preference_set)",
|
|
2076
2291
|
autonomyQ: " How autonomous should I be?\n 1. Conservative — ask before most actions\n 2. Balanced — act on routine, ask on important\n 3. Full — act first, inform after, only ask when truly uncertain\n > ",
|
|
@@ -2101,8 +2316,9 @@ async function main() {
|
|
|
2101
2316
|
dataDirConfirm: (p) => `Directorio de datos: ${p}`,
|
|
2102
2317
|
askUserName: " ¿Cómo te llamas? > ",
|
|
2103
2318
|
userGreet: (n) => `Encantado, ${n}.`,
|
|
2104
|
-
askAgentName:
|
|
2319
|
+
askAgentName: ` ¿Cómo quieres que me llame? (default: ${DEFAULT_ASSISTANT_NAME}) > `,
|
|
2105
2320
|
agentConfirm: (n) => `Perfecto, soy ${n}.`,
|
|
2321
|
+
agentNameReserved: "Ese nombre está reservado para el producto. Elige otro nombre para el asistente.",
|
|
2106
2322
|
calibTitle: "Vamos a calibrar mi personalidad para trabajar mejor contigo.",
|
|
2107
2323
|
calibNote: "(Puedes cambiar esto en cualquier momento con nexo_preference_set)",
|
|
2108
2324
|
autonomyQ: " ¿Cuánta autonomía me das?\n 1. Conservador — pregunto antes de casi todo\n 2. Equilibrado — actúo en lo rutinario, pregunto en lo importante\n 3. Total — actúo primero, informo después, solo pregunto si hay duda real\n > ",
|
|
@@ -2133,8 +2349,9 @@ async function main() {
|
|
|
2133
2349
|
dataDirConfirm: (p) => `Répertoire de données : ${p}`,
|
|
2134
2350
|
askUserName: " Comment tu t'appelles ? > ",
|
|
2135
2351
|
userGreet: (n) => `Enchanté, ${n}.`,
|
|
2136
|
-
askAgentName:
|
|
2352
|
+
askAgentName: ` Comment veux-tu m'appeler ? (défaut : ${DEFAULT_ASSISTANT_NAME}) > `,
|
|
2137
2353
|
agentConfirm: (n) => `C'est noté. Je suis ${n}.`,
|
|
2354
|
+
agentNameReserved: "Ce nom est réservé au produit. Choisis un autre nom pour l'assistant.",
|
|
2138
2355
|
calibTitle: "Calibrons ma personnalité pour mieux travailler ensemble.",
|
|
2139
2356
|
calibNote: "(Tu peux changer ça à tout moment avec nexo_preference_set)",
|
|
2140
2357
|
autonomyQ: " Quel niveau d'autonomie me donnes-tu ?\n 1. Conservateur — je demande avant presque tout\n 2. Équilibré — j'agis en routine, je demande pour l'important\n 3. Total — j'agis d'abord, j'informe après\n > ",
|
|
@@ -2165,8 +2382,9 @@ async function main() {
|
|
|
2165
2382
|
dataDirConfirm: (p) => `Datenverzeichnis: ${p}`,
|
|
2166
2383
|
askUserName: " Wie heißt du? > ",
|
|
2167
2384
|
userGreet: (n) => `Freut mich, ${n}.`,
|
|
2168
|
-
askAgentName:
|
|
2385
|
+
askAgentName: ` Wie soll ich heißen? (Standard: ${DEFAULT_ASSISTANT_NAME}) > `,
|
|
2169
2386
|
agentConfirm: (n) => `Alles klar. Ich bin ${n}.`,
|
|
2387
|
+
agentNameReserved: "Dieser Name ist für das Produkt reserviert. Bitte wähle einen anderen Assistentennamen.",
|
|
2170
2388
|
calibTitle: "Kalibrieren wir meine Persönlichkeit für die Zusammenarbeit.",
|
|
2171
2389
|
calibNote: "(Jederzeit änderbar mit nexo_preference_set)",
|
|
2172
2390
|
autonomyQ: " Wie viel Autonomie gibst du mir?\n 1. Konservativ — frage vor fast allem\n 2. Ausgewogen — handle bei Routine, frage bei Wichtigem\n 3. Voll — handle zuerst, informiere danach\n > ",
|
|
@@ -2197,8 +2415,9 @@ async function main() {
|
|
|
2197
2415
|
dataDirConfirm: (p) => `Directory dati: ${p}`,
|
|
2198
2416
|
askUserName: " Come ti chiami? > ",
|
|
2199
2417
|
userGreet: (n) => `Piacere, ${n}.`,
|
|
2200
|
-
askAgentName:
|
|
2418
|
+
askAgentName: ` Come vuoi chiamarmi? (default: ${DEFAULT_ASSISTANT_NAME}) > `,
|
|
2201
2419
|
agentConfirm: (n) => `Perfetto, sono ${n}.`,
|
|
2420
|
+
agentNameReserved: "Quel nome è riservato al prodotto. Scegli un altro nome per l'assistente.",
|
|
2202
2421
|
calibTitle: "Calibriamo la mia personalità per lavorare meglio insieme.",
|
|
2203
2422
|
calibNote: "(Puoi cambiare in qualsiasi momento con nexo_preference_set)",
|
|
2204
2423
|
autonomyQ: " Quanta autonomia mi dai?\n 1. Conservatore — chiedo prima di quasi tutto\n 2. Equilibrato — agisco nella routine, chiedo per le cose importanti\n 3. Totale — agisco prima, informo dopo\n > ",
|
|
@@ -2229,8 +2448,9 @@ async function main() {
|
|
|
2229
2448
|
dataDirConfirm: (p) => `Diretório de dados: ${p}`,
|
|
2230
2449
|
askUserName: " Como te chamas? > ",
|
|
2231
2450
|
userGreet: (n) => `Prazer, ${n}.`,
|
|
2232
|
-
askAgentName:
|
|
2451
|
+
askAgentName: ` Como queres que eu me chame? (padrão: ${DEFAULT_ASSISTANT_NAME}) > `,
|
|
2233
2452
|
agentConfirm: (n) => `Perfeito, sou ${n}.`,
|
|
2453
|
+
agentNameReserved: "Esse nome está reservado para o produto. Escolhe outro nome para o assistente.",
|
|
2234
2454
|
calibTitle: "Vamos calibrar a minha personalidade para trabalhar melhor contigo.",
|
|
2235
2455
|
calibNote: "(Podes mudar a qualquer momento com nexo_preference_set)",
|
|
2236
2456
|
autonomyQ: " Quanta autonomia me dás?\n 1. Conservador — pergunto antes de quase tudo\n 2. Equilibrado — ajo na rotina, pergunto no importante\n 3. Total — ajo primeiro, informo depois\n > ",
|
|
@@ -2312,8 +2532,19 @@ async function main() {
|
|
|
2312
2532
|
}
|
|
2313
2533
|
|
|
2314
2534
|
// Step 3: Agent name (P3)
|
|
2315
|
-
|
|
2316
|
-
|
|
2535
|
+
let operatorName = DEFAULT_ASSISTANT_NAME;
|
|
2536
|
+
if (!useDefaults) {
|
|
2537
|
+
while (true) {
|
|
2538
|
+
const name = await ask(t.askAgentName);
|
|
2539
|
+
const candidate = name.trim() || DEFAULT_ASSISTANT_NAME;
|
|
2540
|
+
if (!isReservedAssistantName(candidate)) {
|
|
2541
|
+
operatorName = candidate;
|
|
2542
|
+
break;
|
|
2543
|
+
}
|
|
2544
|
+
log(t.agentNameReserved);
|
|
2545
|
+
console.log("");
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2317
2548
|
log(t.agentConfirm(operatorName));
|
|
2318
2549
|
console.log("");
|
|
2319
2550
|
|
|
@@ -2446,6 +2677,24 @@ async function main() {
|
|
|
2446
2677
|
);
|
|
2447
2678
|
} catch (_) {}
|
|
2448
2679
|
|
|
2680
|
+
if (smokeTestMode) {
|
|
2681
|
+
// Pytest fresh-install smoke only needs to prove that the non-interactive
|
|
2682
|
+
// onboarding path writes the current calibration shape. Skip the rest of
|
|
2683
|
+
// the heavy bootstrap (client installs, pip, scan, LaunchAgents) so the
|
|
2684
|
+
// smoke does not sit on long dependency timeouts inside sandboxes.
|
|
2685
|
+
try {
|
|
2686
|
+
const canonicalBrainDir = path.join(NEXO_HOME, "personal", "brain");
|
|
2687
|
+
fs.mkdirSync(canonicalBrainDir, { recursive: true });
|
|
2688
|
+
fs.writeFileSync(
|
|
2689
|
+
path.join(canonicalBrainDir, "calibration.json"),
|
|
2690
|
+
JSON.stringify(calibration, null, 2)
|
|
2691
|
+
);
|
|
2692
|
+
} catch (_) {}
|
|
2693
|
+
log("Smoke test mode detected — wrote calibration and skipped heavy bootstrap.");
|
|
2694
|
+
rl.close();
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2449
2698
|
const clientConfig = await configureClientSetup({
|
|
2450
2699
|
lang,
|
|
2451
2700
|
useDefaults,
|
|
@@ -2596,10 +2845,18 @@ async function main() {
|
|
|
2596
2845
|
' printf \'%s\\n\' "${NEXO_CODE%/}"',
|
|
2597
2846
|
' return 0',
|
|
2598
2847
|
' fi',
|
|
2848
|
+
' if [ -f "$NEXO_HOME/core/cli.py" ]; then',
|
|
2849
|
+
' printf \'%s\\n\' "$NEXO_HOME/core"',
|
|
2850
|
+
' return 0',
|
|
2851
|
+
' fi',
|
|
2599
2852
|
' if [ -f "$NEXO_HOME/cli.py" ]; then',
|
|
2600
2853
|
' printf \'%s\\n\' "$NEXO_HOME"',
|
|
2601
2854
|
' return 0',
|
|
2602
2855
|
' fi',
|
|
2856
|
+
' if [ -d "$NEXO_HOME/core" ]; then',
|
|
2857
|
+
' printf \'%s\\n\' "$NEXO_HOME/core"',
|
|
2858
|
+
' return 0',
|
|
2859
|
+
' fi',
|
|
2603
2860
|
' printf \'%s\\n\' "$NEXO_HOME"',
|
|
2604
2861
|
'}',
|
|
2605
2862
|
'NEXO_CODE="$(resolve_code_dir)"',
|
|
@@ -2641,6 +2898,11 @@ async function main() {
|
|
|
2641
2898
|
' exit 1',
|
|
2642
2899
|
'fi',
|
|
2643
2900
|
'CLI_PY="$NEXO_CODE/cli.py"',
|
|
2901
|
+
'if [ ! -f "$CLI_PY" ] && [ -f "$NEXO_HOME/core/cli.py" ]; then',
|
|
2902
|
+
' NEXO_CODE="$NEXO_HOME/core"',
|
|
2903
|
+
' export NEXO_CODE',
|
|
2904
|
+
' CLI_PY="$NEXO_HOME/core/cli.py"',
|
|
2905
|
+
'fi',
|
|
2644
2906
|
'if [ ! -f "$CLI_PY" ] && [ -f "$NEXO_HOME/cli.py" ]; then',
|
|
2645
2907
|
' NEXO_CODE="$NEXO_HOME"',
|
|
2646
2908
|
' export NEXO_CODE',
|
|
@@ -3308,9 +3570,10 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3308
3570
|
if (!settings.mcpServers) settings.mcpServers = {};
|
|
3309
3571
|
settings.mcpServers.nexo = {
|
|
3310
3572
|
command: python,
|
|
3311
|
-
args: [
|
|
3573
|
+
args: [runtimeServerPath(NEXO_HOME)],
|
|
3312
3574
|
env: {
|
|
3313
3575
|
NEXO_HOME: NEXO_HOME,
|
|
3576
|
+
NEXO_CODE: runtimeCodeDir(NEXO_HOME),
|
|
3314
3577
|
NEXO_NAME: operatorName,
|
|
3315
3578
|
},
|
|
3316
3579
|
};
|
|
@@ -3319,7 +3582,7 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3319
3582
|
if (!settings.hooks) settings.hooks = {};
|
|
3320
3583
|
|
|
3321
3584
|
// Hook scripts already copied above — just reference the dest dir
|
|
3322
|
-
const hooksDestDir =
|
|
3585
|
+
const hooksDestDir = runtimeHooksDir(NEXO_HOME);
|
|
3323
3586
|
|
|
3324
3587
|
registerAllCoreHooks(settings, hooksDestDir, NEXO_HOME);
|
|
3325
3588
|
|
|
@@ -3328,12 +3591,12 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3328
3591
|
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
3329
3592
|
log("MCP server + 8 core hooks configured in Claude Code settings.");
|
|
3330
3593
|
|
|
3331
|
-
const syncClientsScript = path.join(NEXO_HOME, "
|
|
3594
|
+
const syncClientsScript = path.join(runtimeScriptsDir(NEXO_HOME), "nexo-sync-clients.py");
|
|
3332
3595
|
if (fs.existsSync(syncClientsScript)) {
|
|
3333
3596
|
const syncArgs = [
|
|
3334
3597
|
syncClientsScript,
|
|
3335
3598
|
"--nexo-home", NEXO_HOME,
|
|
3336
|
-
"--runtime-root", NEXO_HOME,
|
|
3599
|
+
"--runtime-root", runtimeCodeDir(NEXO_HOME),
|
|
3337
3600
|
"--python", python,
|
|
3338
3601
|
"--operator-name", operatorName,
|
|
3339
3602
|
];
|
|
@@ -3374,11 +3637,9 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3374
3637
|
}
|
|
3375
3638
|
}
|
|
3376
3639
|
|
|
3377
|
-
const claudeCliPath = run("which claude") || "";
|
|
3640
|
+
const claudeCliPath = detectInstalledClients().claude_code.path || run("which claude", { env: buildManagedCliEnv() }) || run("which claude") || "";
|
|
3378
3641
|
if (claudeCliPath) {
|
|
3379
|
-
|
|
3380
|
-
fs.mkdirSync(path.dirname(cliPathFile), { recursive: true });
|
|
3381
|
-
fs.writeFileSync(cliPathFile, claudeCliPath.trim());
|
|
3642
|
+
persistClaudeCliPath(claudeCliPath.trim());
|
|
3382
3643
|
log(`Claude CLI path saved: ${claudeCliPath.trim()}`);
|
|
3383
3644
|
}
|
|
3384
3645
|
|
|
@@ -3391,7 +3652,6 @@ ${doScan ? `- Stack: ${Object.keys(profileData.code.languages || {}).slice(0, 5)
|
|
|
3391
3652
|
schedule = await maybeConfigurePublicContribution(schedule, useDefaults);
|
|
3392
3653
|
schedule = await maybeConfigureFullDiskAccess(schedule, useDefaults, python);
|
|
3393
3654
|
const enabledOptionals = { dashboard: doDashboard, automation: schedule.automation_enabled !== false };
|
|
3394
|
-
const smokeTestMode = process.env.NEXO_TESTING_SMOKE === "1";
|
|
3395
3655
|
if (smokeTestMode) {
|
|
3396
3656
|
log("Smoke test mode detected — skipping LaunchAgents installation.");
|
|
3397
3657
|
} else if (isEphemeralInstall(NEXO_HOME)) {
|
|
@@ -3515,6 +3775,12 @@ See ~/.nexo/ for configuration.
|
|
|
3515
3775
|
log(`CLAUDE.md version tracker initialized: v${claudeMdVersionMatch[1]}`);
|
|
3516
3776
|
}
|
|
3517
3777
|
|
|
3778
|
+
log("Finalizing F0.6 runtime layout...");
|
|
3779
|
+
const layoutFinalize = finalizeF06Layout(python, NEXO_HOME);
|
|
3780
|
+
if (!layoutFinalize.ok) {
|
|
3781
|
+
throw new Error(`F0.6 layout finalization failed: ${layoutFinalize.error}`);
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3518
3784
|
console.log("");
|
|
3519
3785
|
const readyMsg = t.ready(operatorName, aliasName);
|
|
3520
3786
|
const readySub = t.readySubtext;
|
package/bin/nexo.js
CHANGED
|
@@ -42,12 +42,18 @@ function resolveCodeDir() {
|
|
|
42
42
|
if (fs.existsSync(repoCandidate)) {
|
|
43
43
|
return path.join(__dirname, "..", "src");
|
|
44
44
|
}
|
|
45
|
+
if (fs.existsSync(path.join(NEXO_HOME, "core", "cli.py"))) {
|
|
46
|
+
return path.join(NEXO_HOME, "core");
|
|
47
|
+
}
|
|
45
48
|
if (fs.existsSync(path.join(NEXO_HOME, "cli.py"))) {
|
|
46
49
|
return NEXO_HOME;
|
|
47
50
|
}
|
|
48
51
|
if (fs.existsSync(path.join(NEXO_HOME, "claude", "cli.py"))) {
|
|
49
52
|
return path.join(NEXO_HOME, "claude");
|
|
50
53
|
}
|
|
54
|
+
if (fs.existsSync(path.join(NEXO_HOME, "core"))) {
|
|
55
|
+
return path.join(NEXO_HOME, "core");
|
|
56
|
+
}
|
|
51
57
|
return NEXO_HOME;
|
|
52
58
|
}
|
|
53
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain \u2014 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",
|