nexo-brain 6.4.0 → 7.0.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 +1 -1
- package/README.md +8 -1
- package/package.json +2 -2
- package/src/auto_update.py +271 -30
- package/src/bootstrap_docs.py +2 -1
- package/src/calibration_migration.py +5 -2
- package/src/cli.py +69 -6
- package/src/cron_recovery.py +4 -3
- package/src/db/_personal_scripts.py +3 -1
- package/src/db/_skills.py +2 -1
- package/src/desktop_bridge.py +4 -2
- package/src/doctor/providers/boot.py +21 -7
- package/src/doctor/providers/deep.py +6 -5
- package/src/doctor/providers/runtime.py +17 -16
- package/src/evolution_cycle.py +7 -6
- package/src/health_check.py +4 -2
- package/src/paths.py +394 -0
- package/src/plugins/personal_plugins.py +2 -1
- package/src/plugins/recover.py +3 -2
- package/src/plugins/update.py +10 -9
- package/src/public_contribution.py +2 -1
- package/src/runtime_power.py +6 -5
- package/src/script_registry.py +154 -20
- package/src/scripts/nexo-backup.sh +2 -2
- package/src/scripts/nexo-cron-wrapper.sh +40 -2
- package/src/scripts/nexo-deep-sleep.sh +2 -2
- package/src/scripts/nexo-inbox-hook.sh +1 -1
- package/src/scripts/nexo-snapshot-restore.sh +1 -1
- package/src/scripts/nexo-tcc-approve.sh +2 -2
- package/src/scripts/nexo-watchdog.sh +14 -14
- package/src/system_catalog.py +3 -2
- package/src/tools_sessions.py +2 -1
- package/src/user_data_portability.py +9 -8
package/src/paths.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""Plan Consolidado F0.6 — canonical path helpers.
|
|
2
|
+
|
|
3
|
+
Every module that needs to find a runtime directory should import from
|
|
4
|
+
here instead of hardcoding `NEXO_HOME / "scripts"` etc. The legacy
|
|
5
|
+
flat layout (`~/.nexo/scripts/`, `~/.nexo/brain/`, `~/.nexo/data/`,
|
|
6
|
+
...) is going away in v7.0.0; this module centralises the new tree
|
|
7
|
+
so the migration is a one-line change per call site.
|
|
8
|
+
|
|
9
|
+
New structure (post-F0.6):
|
|
10
|
+
~/.nexo/
|
|
11
|
+
├── core/ ← shipped with the package
|
|
12
|
+
│ ├── scripts/ (38 packaged automations)
|
|
13
|
+
│ ├── plugins/
|
|
14
|
+
│ ├── hooks/
|
|
15
|
+
│ ├── rules/
|
|
16
|
+
│ └── contracts/ (resonance_tiers.json, ...)
|
|
17
|
+
├── core-dev/ ← dev-only, off by default
|
|
18
|
+
│ └── scripts/
|
|
19
|
+
├── personal/ ← operator. nexo update never touches.
|
|
20
|
+
│ ├── scripts/
|
|
21
|
+
│ ├── skills/
|
|
22
|
+
│ ├── plugins/
|
|
23
|
+
│ ├── hooks/
|
|
24
|
+
│ ├── rules/
|
|
25
|
+
│ ├── brain/ (calibration.json, project-atlas.json,
|
|
26
|
+
│ │ operator-routing-rules.json, ...)
|
|
27
|
+
│ ├── config/
|
|
28
|
+
│ ├── lib/
|
|
29
|
+
│ └── overrides/
|
|
30
|
+
└── runtime/ ← dynamic state
|
|
31
|
+
├── data/ (nexo.db, *.db)
|
|
32
|
+
├── logs/
|
|
33
|
+
├── operations/
|
|
34
|
+
├── backups/
|
|
35
|
+
├── memory/
|
|
36
|
+
├── cognitive/
|
|
37
|
+
├── coordination/
|
|
38
|
+
├── exports/
|
|
39
|
+
├── nexo-email/
|
|
40
|
+
├── doctor/
|
|
41
|
+
├── snapshots/
|
|
42
|
+
└── crons/
|
|
43
|
+
|
|
44
|
+
Backwards compatibility: every helper has `legacy=True` mode that
|
|
45
|
+
returns the pre-F0.6 location (`NEXO_HOME / "<name>"`). The compat
|
|
46
|
+
layer disappears in v7.1.0; v7.0.0 keeps it so operator-edited code
|
|
47
|
+
that hardcoded the old paths keeps resolving via symlink during the
|
|
48
|
+
1-week observation window.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from __future__ import annotations
|
|
52
|
+
|
|
53
|
+
import os
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def home() -> Path:
|
|
60
|
+
"""Return the active NEXO_HOME (recomputed every call so tests
|
|
61
|
+
that monkeypatch the env var see the right path)."""
|
|
62
|
+
return Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Core (shipped with the package, replaced on every `nexo update`)
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
def core_dir() -> Path:
|
|
69
|
+
return home() / "core"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def core_scripts_dir() -> Path:
|
|
73
|
+
new = core_dir() / "scripts"
|
|
74
|
+
legacy = home() / "scripts"
|
|
75
|
+
if not new.exists() and legacy.exists():
|
|
76
|
+
return legacy
|
|
77
|
+
return new
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def core_plugins_dir() -> Path:
|
|
81
|
+
new = core_dir() / "plugins"
|
|
82
|
+
legacy = home() / "plugins"
|
|
83
|
+
if not new.exists() and legacy.exists():
|
|
84
|
+
return legacy
|
|
85
|
+
return new
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def core_hooks_dir() -> Path:
|
|
89
|
+
new = core_dir() / "hooks"
|
|
90
|
+
legacy = home() / "hooks"
|
|
91
|
+
if not new.exists() and legacy.exists():
|
|
92
|
+
return legacy
|
|
93
|
+
return new
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def core_rules_dir() -> Path:
|
|
97
|
+
new = core_dir() / "rules"
|
|
98
|
+
legacy = home() / "rules"
|
|
99
|
+
if not new.exists() and legacy.exists():
|
|
100
|
+
return legacy
|
|
101
|
+
return new
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def core_contracts_dir() -> Path:
|
|
105
|
+
return core_dir() / "contracts"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# Core-dev (off by default, only useful to product devs)
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
def core_dev_dir() -> Path:
|
|
112
|
+
return home() / "core-dev"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def core_dev_scripts_dir() -> Path:
|
|
116
|
+
return core_dev_dir() / "scripts"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Personal (operator-owned, `nexo update` never touches)
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
def personal_dir() -> Path:
|
|
123
|
+
return home() / "personal"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def personal_scripts_dir() -> Path:
|
|
127
|
+
return personal_dir() / "scripts"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def personal_plugins_dir() -> Path:
|
|
131
|
+
return personal_dir() / "plugins"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def personal_hooks_dir() -> Path:
|
|
135
|
+
return personal_dir() / "hooks"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def personal_rules_dir() -> Path:
|
|
139
|
+
return personal_dir() / "rules"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def personal_skills_dir() -> Path:
|
|
143
|
+
new = personal_dir() / "skills"
|
|
144
|
+
legacy = home() / "skills"
|
|
145
|
+
if not new.exists() and legacy.exists():
|
|
146
|
+
return legacy
|
|
147
|
+
return new
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def brain_dir() -> Path:
|
|
151
|
+
"""Operator brain: calibration, project atlas, routing rules, ..."""
|
|
152
|
+
new = personal_dir() / "brain"
|
|
153
|
+
legacy = home() / "brain"
|
|
154
|
+
if not new.exists() and legacy.exists():
|
|
155
|
+
return legacy
|
|
156
|
+
return new
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def personal_config_dir() -> Path:
|
|
160
|
+
return personal_dir() / "config"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def personal_lib_dir() -> Path:
|
|
164
|
+
return personal_dir() / "lib"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def personal_overrides_dir() -> Path:
|
|
168
|
+
return personal_dir() / "overrides"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
# Runtime (dynamic state, never edited by hand)
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
def runtime_dir() -> Path:
|
|
175
|
+
return home() / "runtime"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def data_dir() -> Path:
|
|
179
|
+
new = runtime_dir() / "data"
|
|
180
|
+
legacy = home() / "data"
|
|
181
|
+
if not new.exists() and legacy.exists():
|
|
182
|
+
return legacy
|
|
183
|
+
return new
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def db_path() -> Path:
|
|
187
|
+
new = data_dir() / "nexo.db"
|
|
188
|
+
legacy = home() / "data" / "nexo.db"
|
|
189
|
+
if not new.is_file() and legacy.is_file():
|
|
190
|
+
return legacy
|
|
191
|
+
return new
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def logs_dir() -> Path:
|
|
195
|
+
new = runtime_dir() / "logs"
|
|
196
|
+
legacy = home() / "logs"
|
|
197
|
+
if not new.exists() and legacy.exists():
|
|
198
|
+
return legacy
|
|
199
|
+
return new
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def operations_dir() -> Path:
|
|
203
|
+
new = runtime_dir() / "operations"
|
|
204
|
+
legacy = home() / "operations"
|
|
205
|
+
if not new.exists() and legacy.exists():
|
|
206
|
+
return legacy
|
|
207
|
+
return new
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def backups_dir() -> Path:
|
|
211
|
+
new = runtime_dir() / "backups"
|
|
212
|
+
legacy = home() / "backups"
|
|
213
|
+
if not new.exists() and legacy.exists():
|
|
214
|
+
return legacy
|
|
215
|
+
return new
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def memory_dir() -> Path:
|
|
219
|
+
new = runtime_dir() / "memory"
|
|
220
|
+
legacy = home() / "memory"
|
|
221
|
+
if not new.exists() and legacy.exists():
|
|
222
|
+
return legacy
|
|
223
|
+
return new
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def cognitive_dir() -> Path:
|
|
227
|
+
new = runtime_dir() / "cognitive"
|
|
228
|
+
legacy = home() / "cognitive"
|
|
229
|
+
if not new.exists() and legacy.exists():
|
|
230
|
+
return legacy
|
|
231
|
+
return new
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def coordination_dir() -> Path:
|
|
235
|
+
new = runtime_dir() / "coordination"
|
|
236
|
+
legacy = home() / "coordination"
|
|
237
|
+
if not new.exists() and legacy.exists():
|
|
238
|
+
return legacy
|
|
239
|
+
return new
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def exports_dir() -> Path:
|
|
243
|
+
new = runtime_dir() / "exports"
|
|
244
|
+
legacy = home() / "exports"
|
|
245
|
+
if not new.exists() and legacy.exists():
|
|
246
|
+
return legacy
|
|
247
|
+
return new
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def nexo_email_dir() -> Path:
|
|
251
|
+
new = runtime_dir() / "nexo-email"
|
|
252
|
+
legacy = home() / "nexo-email"
|
|
253
|
+
if not new.exists() and legacy.exists():
|
|
254
|
+
return legacy
|
|
255
|
+
return new
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def doctor_dir() -> Path:
|
|
259
|
+
new = runtime_dir() / "doctor"
|
|
260
|
+
legacy = home() / "doctor"
|
|
261
|
+
if not new.exists() and legacy.exists():
|
|
262
|
+
return legacy
|
|
263
|
+
return new
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def snapshots_dir() -> Path:
|
|
267
|
+
new = runtime_dir() / "snapshots"
|
|
268
|
+
legacy = home() / "snapshots"
|
|
269
|
+
if not new.exists() and legacy.exists():
|
|
270
|
+
return legacy
|
|
271
|
+
return new
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def crons_dir() -> Path:
|
|
275
|
+
new = runtime_dir() / "crons"
|
|
276
|
+
legacy = home() / "crons"
|
|
277
|
+
if not new.exists() and legacy.exists():
|
|
278
|
+
return legacy
|
|
279
|
+
return new
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# ---------------------------------------------------------------------------
|
|
283
|
+
# Combined views (for callers that need to scan core+personal merged)
|
|
284
|
+
# ---------------------------------------------------------------------------
|
|
285
|
+
def all_scripts_dirs() -> list[Path]:
|
|
286
|
+
"""Return every directory `nexo scripts list` should scan."""
|
|
287
|
+
return [core_scripts_dir(), personal_scripts_dir(), core_dev_scripts_dir()]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def all_plugins_dirs() -> list[Path]:
|
|
291
|
+
return [core_plugins_dir(), personal_plugins_dir()]
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def all_hooks_dirs() -> list[Path]:
|
|
295
|
+
return [core_hooks_dir(), personal_hooks_dir()]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def all_rules_dirs() -> list[Path]:
|
|
299
|
+
return [core_rules_dir(), personal_rules_dir()]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
# Legacy compat (PRE-F0.6 paths). Every shipped runtime keeps these as
|
|
304
|
+
# symlinks to the new locations until v7.1.0, so operator code that
|
|
305
|
+
# hardcoded the flat layout continues to resolve.
|
|
306
|
+
# ---------------------------------------------------------------------------
|
|
307
|
+
def legacy_scripts_dir() -> Path:
|
|
308
|
+
return home() / "scripts"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def legacy_brain_dir() -> Path:
|
|
312
|
+
return home() / "brain"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def legacy_data_dir() -> Path:
|
|
316
|
+
return home() / "data"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def legacy_logs_dir() -> Path:
|
|
320
|
+
return home() / "logs"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def legacy_operations_dir() -> Path:
|
|
324
|
+
return home() / "operations"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def legacy_db_path() -> Path:
|
|
328
|
+
return legacy_data_dir() / "nexo.db"
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
# Smart resolver: prefer new location if it exists, fall back to legacy.
|
|
333
|
+
# Used during the v7.0.0 / v7.1.0 transition window.
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
def resolve_db_path() -> Path:
|
|
336
|
+
"""Return the active SQLite DB path, preferring the new location
|
|
337
|
+
but falling back to the legacy one when an older runtime hasn't
|
|
338
|
+
migrated yet."""
|
|
339
|
+
new = db_path()
|
|
340
|
+
if new.is_file():
|
|
341
|
+
return new
|
|
342
|
+
legacy = legacy_db_path()
|
|
343
|
+
if legacy.is_file():
|
|
344
|
+
return legacy
|
|
345
|
+
return new # default: new layout for fresh installs
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
__all__ = [
|
|
349
|
+
"NEXO_HOME",
|
|
350
|
+
"home",
|
|
351
|
+
"core_dir",
|
|
352
|
+
"core_scripts_dir",
|
|
353
|
+
"core_plugins_dir",
|
|
354
|
+
"core_hooks_dir",
|
|
355
|
+
"core_rules_dir",
|
|
356
|
+
"core_contracts_dir",
|
|
357
|
+
"core_dev_dir",
|
|
358
|
+
"core_dev_scripts_dir",
|
|
359
|
+
"personal_dir",
|
|
360
|
+
"personal_scripts_dir",
|
|
361
|
+
"personal_plugins_dir",
|
|
362
|
+
"personal_hooks_dir",
|
|
363
|
+
"personal_rules_dir",
|
|
364
|
+
"personal_skills_dir",
|
|
365
|
+
"brain_dir",
|
|
366
|
+
"personal_config_dir",
|
|
367
|
+
"personal_lib_dir",
|
|
368
|
+
"personal_overrides_dir",
|
|
369
|
+
"runtime_dir",
|
|
370
|
+
"data_dir",
|
|
371
|
+
"db_path",
|
|
372
|
+
"logs_dir",
|
|
373
|
+
"operations_dir",
|
|
374
|
+
"backups_dir",
|
|
375
|
+
"memory_dir",
|
|
376
|
+
"cognitive_dir",
|
|
377
|
+
"coordination_dir",
|
|
378
|
+
"exports_dir",
|
|
379
|
+
"nexo_email_dir",
|
|
380
|
+
"doctor_dir",
|
|
381
|
+
"snapshots_dir",
|
|
382
|
+
"crons_dir",
|
|
383
|
+
"all_scripts_dirs",
|
|
384
|
+
"all_plugins_dirs",
|
|
385
|
+
"all_hooks_dirs",
|
|
386
|
+
"all_rules_dirs",
|
|
387
|
+
"legacy_scripts_dir",
|
|
388
|
+
"legacy_brain_dir",
|
|
389
|
+
"legacy_data_dir",
|
|
390
|
+
"legacy_logs_dir",
|
|
391
|
+
"legacy_operations_dir",
|
|
392
|
+
"legacy_db_path",
|
|
393
|
+
"resolve_db_path",
|
|
394
|
+
]
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import paths
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from db import init_db
|
|
@@ -15,7 +16,7 @@ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parent
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def _plugins_dir() -> Path:
|
|
18
|
-
path =
|
|
19
|
+
path = paths.core_plugins_dir()
|
|
19
20
|
path.mkdir(parents=True, exist_ok=True)
|
|
20
21
|
return path
|
|
21
22
|
|
package/src/plugins/recover.py
CHANGED
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import json
|
|
21
21
|
import os
|
|
22
|
+
import paths
|
|
22
23
|
import re
|
|
23
24
|
import sys
|
|
24
25
|
import time
|
|
@@ -46,8 +47,8 @@ from db_guard import (
|
|
|
46
47
|
)
|
|
47
48
|
|
|
48
49
|
NEXO_HOME = export_resolved_nexo_home()
|
|
49
|
-
DATA_DIR =
|
|
50
|
-
BACKUP_BASE =
|
|
50
|
+
DATA_DIR = paths.data_dir()
|
|
51
|
+
BACKUP_BASE = paths.backups_dir()
|
|
51
52
|
PRIMARY_DB = DATA_DIR / "nexo.db"
|
|
52
53
|
|
|
53
54
|
|
package/src/plugins/update.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
"""Update plugin — pull latest code, backup DBs, run migrations, verify."""
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import paths
|
|
5
6
|
import re
|
|
6
7
|
import shutil
|
|
7
8
|
import sqlite3
|
|
@@ -84,8 +85,8 @@ CODE_ROOT = _THIS_DIR.parent
|
|
|
84
85
|
_REPO_CANDIDATE = CODE_ROOT.parent
|
|
85
86
|
|
|
86
87
|
NEXO_HOME = export_resolved_nexo_home()
|
|
87
|
-
DATA_DIR =
|
|
88
|
-
BACKUP_BASE =
|
|
88
|
+
DATA_DIR = paths.data_dir()
|
|
89
|
+
BACKUP_BASE = paths.backups_dir()
|
|
89
90
|
|
|
90
91
|
# In packaged installs, update.py lives at <NEXO_HOME>/plugins/update.py.
|
|
91
92
|
_PACKAGED_INSTALL = not (_REPO_CANDIDATE / ".git").exists() and not (_REPO_CANDIDATE / ".git").is_file()
|
|
@@ -161,7 +162,7 @@ def _refresh_installed_manifest():
|
|
|
161
162
|
return
|
|
162
163
|
|
|
163
164
|
src_crons = artifact_src / "crons"
|
|
164
|
-
dst_crons =
|
|
165
|
+
dst_crons = paths.crons_dir()
|
|
165
166
|
if src_crons.exists():
|
|
166
167
|
dst_crons.mkdir(parents=True, exist_ok=True)
|
|
167
168
|
for f in src_crons.iterdir():
|
|
@@ -193,10 +194,10 @@ def _refresh_installed_manifest():
|
|
|
193
194
|
def _cleanup_retired_runtime_files() -> list[str]:
|
|
194
195
|
removed: list[str] = []
|
|
195
196
|
retired_paths = [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
paths.core_scripts_dir() / "heartbeat-enforcement.py",
|
|
198
|
+
paths.core_scripts_dir() / "heartbeat-posttool.sh",
|
|
199
|
+
paths.core_scripts_dir() / "heartbeat-user-msg.sh",
|
|
200
|
+
paths.core_hooks_dir() / "heartbeat-guard.sh",
|
|
200
201
|
]
|
|
201
202
|
for path in retired_paths:
|
|
202
203
|
if not path.exists():
|
|
@@ -723,7 +724,7 @@ def _sync_hooks_to_home():
|
|
|
723
724
|
"""Copy hook scripts from src/hooks/ to NEXO_HOME/hooks/ after update."""
|
|
724
725
|
import shutil
|
|
725
726
|
hooks_src = SRC_DIR / "hooks"
|
|
726
|
-
hooks_dest =
|
|
727
|
+
hooks_dest = paths.core_hooks_dir()
|
|
727
728
|
if not hooks_src.is_dir():
|
|
728
729
|
return
|
|
729
730
|
hooks_dest.mkdir(parents=True, exist_ok=True)
|
|
@@ -893,7 +894,7 @@ def _paths_match(src: Path, dest: Path) -> bool:
|
|
|
893
894
|
|
|
894
895
|
|
|
895
896
|
def _sync_packaged_crons(progress_fn=None) -> tuple[bool, str | None]:
|
|
896
|
-
sync_path =
|
|
897
|
+
sync_path = paths.crons_dir() / "sync.py"
|
|
897
898
|
if not sync_path.is_file():
|
|
898
899
|
_refresh_installed_manifest()
|
|
899
900
|
return True, None
|
|
@@ -9,6 +9,7 @@ This module manages the opt-in "public core evolution" mode:
|
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import paths
|
|
12
13
|
import platform
|
|
13
14
|
import re
|
|
14
15
|
import shutil
|
|
@@ -47,7 +48,7 @@ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
|
47
48
|
CONTRIB_ROOT = NEXO_HOME / "contrib" / "public-core"
|
|
48
49
|
CONTRIB_REPO_DIR = CONTRIB_ROOT / "repo"
|
|
49
50
|
CONTRIB_WORKTREES_DIR = CONTRIB_ROOT / "worktrees"
|
|
50
|
-
CONTRIB_ARTIFACTS_DIR =
|
|
51
|
+
CONTRIB_ARTIFACTS_DIR = paths.operations_dir() / "public-contrib"
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
def _utcnow() -> datetime:
|
package/src/runtime_power.py
CHANGED
|
@@ -15,6 +15,7 @@ Important semantic note:
|
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
17
|
import os
|
|
18
|
+
import paths
|
|
18
19
|
import platform
|
|
19
20
|
import plistlib
|
|
20
21
|
import shutil
|
|
@@ -336,7 +337,7 @@ def detect_full_disk_access_reasons(*, system: str | None = None) -> list[str]:
|
|
|
336
337
|
f"NEXO_HOME is inside a protected macOS folder: {NEXO_HOME}"
|
|
337
338
|
)
|
|
338
339
|
|
|
339
|
-
logs_dir =
|
|
340
|
+
logs_dir = paths.logs_dir()
|
|
340
341
|
if logs_dir.is_dir():
|
|
341
342
|
for log_file in sorted(logs_dir.glob("*-stderr.log")):
|
|
342
343
|
if _tail_has_permission_denial(log_file):
|
|
@@ -715,7 +716,7 @@ def ensure_full_disk_access_choice(
|
|
|
715
716
|
|
|
716
717
|
|
|
717
718
|
def _prevent_sleep_script_path() -> Path:
|
|
718
|
-
runtime_script =
|
|
719
|
+
runtime_script = paths.core_scripts_dir() / "nexo-prevent-sleep.sh"
|
|
719
720
|
if runtime_script.is_file():
|
|
720
721
|
return runtime_script
|
|
721
722
|
source_script = NEXO_CODE / "scripts" / "nexo-prevent-sleep.sh"
|
|
@@ -730,8 +731,8 @@ def _macos_prevent_sleep_plist() -> tuple[Path, dict]:
|
|
|
730
731
|
"ProgramArguments": ["/bin/bash", str(script_path)],
|
|
731
732
|
"RunAtLoad": True,
|
|
732
733
|
"KeepAlive": True,
|
|
733
|
-
"StandardOutPath": str(
|
|
734
|
-
"StandardErrorPath": str(
|
|
734
|
+
"StandardOutPath": str(paths.logs_dir() / "prevent-sleep-stdout.log"),
|
|
735
|
+
"StandardErrorPath": str(paths.logs_dir() / "prevent-sleep-stderr.log"),
|
|
735
736
|
"EnvironmentVariables": {
|
|
736
737
|
"HOME": str(Path.home()),
|
|
737
738
|
"NEXO_HOME": str(NEXO_HOME),
|
|
@@ -766,7 +767,7 @@ WantedBy=default.target
|
|
|
766
767
|
def apply_power_policy(policy: str | None = None) -> dict:
|
|
767
768
|
policy = normalize_power_policy(policy or get_power_policy())
|
|
768
769
|
system = platform.system()
|
|
769
|
-
logs_dir =
|
|
770
|
+
logs_dir = paths.logs_dir()
|
|
770
771
|
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
771
772
|
details = describe_power_policy(policy=policy, system=system)
|
|
772
773
|
|