brainiac-cli 0.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. brain/__init__.py +39 -0
  2. brain/__main__.py +14 -0
  3. brain/_assets/AGENTS.md +564 -0
  4. brain/_assets/overlay/template/brand/brand-guide.md +24 -0
  5. brain/_assets/overlay/template/keywords/glossary.md +15 -0
  6. brain/_assets/overlay/template/people/roster.md +14 -0
  7. brain/_assets/overlay/template/voice/voice-profile.md +34 -0
  8. brain/_assets/routines/manifest.json +257 -0
  9. brain/_assets/scripts/brain-brief-mac.plist +59 -0
  10. brain/_assets/scripts/brain-brief.sh +74 -0
  11. brain/_assets/scripts/brain-synthesis-mac.plist +48 -0
  12. brain/_assets/scripts/brain-synthesis.sh +214 -0
  13. brain/_assets/scripts/install-brief-mac.sh +152 -0
  14. brain/_assets/scripts/install-brief-windows.ps1 +97 -0
  15. brain/_assets/scripts/register_tasks.py +386 -0
  16. brain/_assets/templates/company.md +21 -0
  17. brain/_assets/templates/concept.md +27 -0
  18. brain/_assets/templates/daily.md +20 -0
  19. brain/_assets/templates/decision.md +42 -0
  20. brain/_assets/templates/meeting.md +33 -0
  21. brain/_assets/templates/person.md +20 -0
  22. brain/_assets/templates/project.md +23 -0
  23. brain/_assets/templates/state-moc.md +40 -0
  24. brain/_version.py +12 -0
  25. brain/anchor.py +121 -0
  26. brain/audit.py +422 -0
  27. brain/backup.py +210 -0
  28. brain/brief.py +417 -0
  29. brain/capture.py +117 -0
  30. brain/chunk.py +249 -0
  31. brain/classification.py +134 -0
  32. brain/cli.py +1906 -0
  33. brain/config.py +368 -0
  34. brain/connect.py +362 -0
  35. brain/context.py +108 -0
  36. brain/core.py +3018 -0
  37. brain/doctor.py +1161 -0
  38. brain/egress.py +148 -0
  39. brain/embed.py +857 -0
  40. brain/encryption.py +217 -0
  41. brain/frontmatter.py +102 -0
  42. brain/golden_probe.py +678 -0
  43. brain/graph.py +369 -0
  44. brain/graphify.py +352 -0
  45. brain/index.py +1576 -0
  46. brain/ingest/__init__.py +19 -0
  47. brain/ingest/handlers/__init__.py +43 -0
  48. brain/ingest/handlers/base.py +95 -0
  49. brain/ingest/handlers/docx.py +78 -0
  50. brain/ingest/handlers/email.py +228 -0
  51. brain/ingest/handlers/html.py +142 -0
  52. brain/ingest/handlers/image.py +91 -0
  53. brain/ingest/handlers/pdf.py +99 -0
  54. brain/ingest/handlers/pptx.py +69 -0
  55. brain/ingest/handlers/tables.py +41 -0
  56. brain/ingest/handlers/text.py +43 -0
  57. brain/ingest/handlers/xlsx.py +100 -0
  58. brain/ingest/handlers/zip.py +163 -0
  59. brain/ingest/pipeline.py +839 -0
  60. brain/ingest/transcript.py +158 -0
  61. brain/init.py +870 -0
  62. brain/maintenance.py +2266 -0
  63. brain/mcp_adapter.py +217 -0
  64. brain/multihop.py +232 -0
  65. brain/notes.py +195 -0
  66. brain/overlay.py +183 -0
  67. brain/projection.py +79 -0
  68. brain/rerank.py +425 -0
  69. brain/snapshot.py +231 -0
  70. brain/update.py +743 -0
  71. brain/vectors.py +225 -0
  72. brainiac_cli-0.16.0.dist-info/METADATA +306 -0
  73. brainiac_cli-0.16.0.dist-info/RECORD +77 -0
  74. brainiac_cli-0.16.0.dist-info/WHEEL +5 -0
  75. brainiac_cli-0.16.0.dist-info/entry_points.txt +4 -0
  76. brainiac_cli-0.16.0.dist-info/licenses/LICENSE +202 -0
  77. brainiac_cli-0.16.0.dist-info/top_level.txt +1 -0
brain/config.py ADDED
@@ -0,0 +1,368 @@
1
+ """Path + location policy for the brain engine.
2
+
3
+ The derived SQLite index lives under a per-user **application-data** directory,
4
+ NOT under Documents/Desktop (Windows Controlled-Folder-Access protected paths) —
5
+ CORE-01 hardening. The index is derived and disposable; delete-and-rebuild from
6
+ `vault/` is always safe, so its exact location is policy, not truth.
7
+
8
+ Resolution order for the index directory:
9
+ 1. ``$BRAIN_INDEX_DIR`` (explicit override; tests use this)
10
+ 2. Windows : ``%LOCALAPPDATA%\\profile-a-brain``
11
+ 3. macOS : ``~/Library/Application Support/profile-a-brain``
12
+ 4. Linux/* : ``$XDG_DATA_HOME/profile-a-brain`` or ``~/.local/share/...``
13
+
14
+ Per-vault isolation (0.3.0): under the app-data base, each vault gets its own
15
+ subdirectory ``vaults/<name>-<hash8>/`` derived from the resolved vault path —
16
+ N vaults on one machine get N independent indexes + audit chains with no env
17
+ var to remember. ``$BRAIN_INDEX_DIR`` still overrides completely (no nesting).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import hashlib
22
+ import os
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ APP_NAME = "profile-a-brain"
27
+ INDEX_FILENAME = "index.sqlite"
28
+
29
+ # Host/VM trust split (S06). The HOST broker is the sole writer (signs the audit
30
+ # chain, mutates the index, publishes snapshots). The Cowork Linux VM is a
31
+ # READ + DRAFT surface only — it may never write notes, open the index in
32
+ # WAL/write mode, or resolve a signing key. Role is resolved from $BRAIN_ROLE,
33
+ # default "host". See AGENTS.md §6 + docs/cowork-windows-install.md.
34
+ ROLE_HOST = "host"
35
+ ROLE_VM = "vm"
36
+
37
+
38
+ def role(explicit: str | None = None) -> str:
39
+ """Resolve the trust role: explicit arg > ``$BRAIN_ROLE`` > ``host``."""
40
+ val = (explicit or os.environ.get("BRAIN_ROLE") or ROLE_HOST).strip().lower()
41
+ return ROLE_VM if val == ROLE_VM else ROLE_HOST
42
+
43
+
44
+ def apply_role_embedder_policy(resolved_role: str) -> None:
45
+ """The VM leg fails CLOSED on a dead embedder by default (DV-03, 2026-07-09).
46
+
47
+ A Cowork VM that silently answered semantic queries with random HASH vectors
48
+ (onnxruntime missing in the zero-install shim's python) is the exact failure
49
+ this guards: ``role=vm`` defaults ``$BRAIN_REQUIRE_REAL_EMBEDDER=1`` so the
50
+ implicit hash fallback RAISES instead of degrading. It is a no-op whenever a
51
+ real embedder is present (the flag only bites on a dead one), and lexical
52
+ verbs (``grep``/``bases-query``) never embed, so they keep working — only the
53
+ semantic path (``search``/``hybrid-search``) fails loud. Skipped when the
54
+ operator explicitly chose hash (``$BRAIN_EMBEDDER=hash``) or already pinned
55
+ the flag either way. Host leg is unchanged (warns, never fails closed)."""
56
+ if resolved_role != ROLE_VM:
57
+ return
58
+ if os.environ.get("BRAIN_EMBEDDER", "").strip().lower() == "hash":
59
+ return
60
+ os.environ.setdefault("BRAIN_REQUIRE_REAL_EMBEDDER", "1")
61
+
62
+
63
+ def _app_data_base() -> Path:
64
+ """Per-user app-data base dir (no vault scoping).
65
+
66
+ Never returns a Controlled-Folder-Access path (Documents/Desktop/Pictures).
67
+ """
68
+ if sys.platform.startswith("win"):
69
+ base = os.environ.get("LOCALAPPDATA") or str(Path.home() / "AppData" / "Local")
70
+ return Path(base) / APP_NAME
71
+ if sys.platform == "darwin":
72
+ return Path.home() / "Library" / "Application Support" / APP_NAME
73
+ # Linux / BSD / Cowork VM
74
+ base = os.environ.get("XDG_DATA_HOME") or str(Path.home() / ".local" / "share")
75
+ return Path(base) / APP_NAME
76
+
77
+
78
+ def index_dir(vault: str | os.PathLike[str] | None = None) -> Path:
79
+ """Per-vault app-data directory holding this vault's index + audit chain.
80
+
81
+ ``$BRAIN_INDEX_DIR`` overrides completely (returned as-is, no per-vault
82
+ nesting — tests and constrained deployments rely on that). Otherwise each
83
+ resolved vault path maps to its own ``vaults/<name>-<hash8>/`` subdir, so
84
+ any number of vaults coexist without sharing an index or audit chain.
85
+ """
86
+ override = os.environ.get("BRAIN_INDEX_DIR")
87
+ if override:
88
+ return Path(override).expanduser()
89
+ v = vault_root(vault)
90
+ slug = f"{v.name}-{hashlib.sha256(str(v).encode()).hexdigest()[:8]}"
91
+ return _app_data_base() / "vaults" / slug
92
+
93
+
94
+ def index_path(vault: str | os.PathLike[str] | None = None) -> Path:
95
+ """Absolute path to this vault's SQLite index file."""
96
+ return index_dir(vault) / INDEX_FILENAME
97
+
98
+
99
+ def ensure_index_dir(vault: str | os.PathLike[str] | None = None) -> Path:
100
+ d = index_dir(vault)
101
+ d.mkdir(parents=True, exist_ok=True)
102
+ return d
103
+
104
+
105
+ def default_audit_log(vault: str | os.PathLike[str] | None = None) -> Path:
106
+ """Default per-vault audit-chain path, with a one-time legacy notice.
107
+
108
+ Pre-0.3.0 installs kept ONE global index + audit chain directly under the
109
+ app-data base. The index is a disposable cache (just rebuild), but the old
110
+ audit chain must not silently disappear: it stays frozen at the legacy
111
+ path — verifiable there forever — and new writes start a fresh per-vault
112
+ chain (same model as a key rotation, see SECURITY.md).
113
+ """
114
+ log = index_dir(vault) / "audit_chain.jsonl"
115
+ legacy = _app_data_base() / "audit_chain.jsonl"
116
+ if not os.environ.get("BRAIN_INDEX_DIR") and legacy.exists() and not log.exists():
117
+ print(
118
+ f"brain: NOTE — a pre-0.3.0 global audit chain exists at {legacy}. "
119
+ f"It stays there, frozen and verifiable; new writes chain at {log}.",
120
+ file=sys.stderr,
121
+ )
122
+ return log
123
+
124
+
125
+ # --------------------------------------------------------------------------
126
+ # file permission policy (hardening pass)
127
+ # --------------------------------------------------------------------------
128
+ # The derived SQLite index and the published read-only snapshot can carry note
129
+ # bodies up to and including MNPI-tier content (the classification gate is an
130
+ # egress *decision*, not containment -- see docs/operations/egress-provider-
131
+ # posture.md §2). Neither must ever be left world-readable. The snapshot was
132
+ # previously chmod'd 0o444 (read-only, but readable by every local account on a
133
+ # shared/multi-user machine); the index inherited whatever the process umask
134
+ # happened to be (often 0o644 on a typical single-user default). Both are now
135
+ # tightened to owner-only immediately after creation, regardless of umask.
136
+ SECURE_FILE_MODE = 0o600 # owner rw only; use 0o640 if a deployment intentionally
137
+ # shares index/snapshot files with a trusted local group
138
+
139
+
140
+ def secure_file_permissions(path: "os.PathLike[str] | str", mode: int = SECURE_FILE_MODE) -> None:
141
+ """Best-effort tighten ``path`` to ``mode`` (default owner-only 0600).
142
+
143
+ Never raises: a chmod call that fails (unsupported filesystem, Windows ACL
144
+ semantics where POSIX mode bits are only partially honored, a race where the
145
+ file vanished) must not break index/snapshot creation -- it degrades to
146
+ "as restrictive as the platform default allowed", not a crash.
147
+ """
148
+ try:
149
+ os.chmod(path, mode)
150
+ except OSError:
151
+ pass
152
+
153
+
154
+ def vault_root(explicit: str | os.PathLike[str] | None = None) -> Path:
155
+ """Resolve the vault root: explicit arg > ``$BRAIN_VAULT`` > CWD/vault.
156
+
157
+ When it falls back to CWD/vault, warn (stderr) if ``./vault`` is not yet a
158
+ Brainiac vault (no ``./vault/.brain``) — so brain never SILENTLY writes to a
159
+ phantom ``./vault/.brain/`` in whatever directory it happened to run from (a
160
+ footgun that once scattered 231 drafts into a stray ``migration/vault/``
161
+ mid-migration). Creation flows (``brain init``, the installer's sample-vault
162
+ build) still work: the fallback path is unchanged — only now it is loud.
163
+ """
164
+ if explicit:
165
+ return Path(explicit).expanduser().resolve()
166
+ env = os.environ.get("BRAIN_VAULT")
167
+ if env:
168
+ return Path(env).expanduser().resolve()
169
+ cwd_vault = (Path.cwd() / "vault").resolve()
170
+ if not (cwd_vault / ".brain").is_dir():
171
+ import sys as _sys
172
+ print(
173
+ f"brain: WARNING no --vault/$BRAIN_VAULT given; resolved to {cwd_vault}, "
174
+ f"which is not yet a vault (no {cwd_vault / '.brain'}). This is the "
175
+ f"CWD/vault fallback ({Path.cwd()} + 'vault'), not an error -- but if this "
176
+ f"isn't the vault you meant, pin it: export BRAIN_VAULT={cwd_vault} "
177
+ "(or pass --vault explicitly on every call). Otherwise brain creates/uses "
178
+ "a vault at the path above.",
179
+ file=_sys.stderr,
180
+ )
181
+ return cwd_vault
182
+
183
+
184
+ def vault_slug8(vault: str | os.PathLike[str] | None = None) -> str:
185
+ """The 8-hex per-vault id — the SAME hash the per-vault app-data dir uses
186
+ (see ``index_dir``). One vault => one stable id, distinct vaults => distinct
187
+ ids, so per-vault artifacts (index, audit chain, nightly task) never collide."""
188
+ return hashlib.sha256(str(vault_root(vault)).encode()).hexdigest()[:8]
189
+
190
+
191
+ def nightly_label(vault: str | os.PathLike[str] | None = None) -> str:
192
+ """launchd label (macOS) for this vault's nightly maintenance task, made
193
+ PER-VAULT so two registered vaults don't install to one shared label and
194
+ clobber each other's job. The legacy single label
195
+ ``com.profile-a-brain.daily-brief`` is migrated away from on next install."""
196
+ return f"com.brainiac.nightly.{vault_slug8(vault)}"
197
+
198
+
199
+ # --------------------------------------------------------------------------
200
+ # workspace runtime locations (S06 — Cowork-Windows workspace-install path)
201
+ # --------------------------------------------------------------------------
202
+ # The Cowork Linux VM mounts ONLY the workspace and sees ``vault/.brain/``. The
203
+ # runtime dir holds the per-arch ``brain`` binary, the bundled ``model.onnx``,
204
+ # the read-only published ``snapshot/`` the VM reads, and the writable
205
+ # ``capture-inbox/`` the VM drops drafts into. All four resolve from env first so
206
+ # a workspace install can point them at a workspace-root ``.brain/`` if desired;
207
+ # the default keeps everything under the gitignored ``vault/.brain/`` (spec §2),
208
+ # which ``notes.scan_vault`` already excludes from indexing.
209
+ def brain_runtime_dir(vault: str | os.PathLike[str] | None = None) -> Path:
210
+ override = os.environ.get("BRAIN_RUNTIME_DIR")
211
+ if override:
212
+ return Path(override).expanduser()
213
+ return vault_root(vault) / ".brain"
214
+
215
+
216
+ def snapshot_dir(vault: str | os.PathLike[str] | None = None) -> Path:
217
+ """Dir holding the read-only published snapshot (DB + manifest)."""
218
+ override = os.environ.get("BRAIN_SNAPSHOT_DIR")
219
+ if override:
220
+ return Path(override).expanduser()
221
+ return brain_runtime_dir(vault) / "snapshot"
222
+
223
+
224
+ def snapshot_db_path(vault: str | os.PathLike[str] | None = None) -> Path:
225
+ """Absolute path to the read-only snapshot DB the VM ``brain`` reads."""
226
+ from .snapshot import SNAPSHOT_DB
227
+
228
+ return snapshot_dir(vault) / SNAPSHOT_DB
229
+
230
+
231
+ def capture_inbox_dir(vault: str | os.PathLike[str] | None = None) -> Path:
232
+ """Writable dir the VM drops capture drafts into (host drains it on invoke).
233
+
234
+ Lives under ``.brain/`` so it is host-visible on the shared mount AND
235
+ excluded from ``scan_vault`` — a draft is never auto-indexed; only the host
236
+ promotes it (sign + index) via drain-on-invoke.
237
+ """
238
+ override = os.environ.get("BRAIN_CAPTURE_INBOX")
239
+ if override:
240
+ return Path(override).expanduser()
241
+ return brain_runtime_dir(vault) / "capture-inbox"
242
+
243
+
244
+ def memory_dir(vault: str | os.PathLike[str] | None = None) -> Path:
245
+ """Session-memory dir (ADR-0003 Ruling 4, MEM-01/02) — handoff.md, hot.md,
246
+ lessons.md, archive/. Host-only, never indexed (under ``.brain/``)."""
247
+ return brain_runtime_dir(vault) / "memory"
248
+
249
+
250
+ def brief_dir(vault: str | os.PathLike[str] | None = None) -> Path:
251
+ """Generated HTML brief/digest dir (AUT-01/AUT-03, ADR-0003 Ruling c) —
252
+ gitignored, local, snapshot-adjacent (under ``.brain/``). HOST-ONLY: never
253
+ committed, never published into the VM snapshot."""
254
+ return brain_runtime_dir(vault) / "brief"
255
+
256
+
257
+ def recommendations_open_path(vault: str | os.PathLike[str] | None = None) -> Path:
258
+ """Open-recommendations JSONL (MEM-03) — one JSON object per line, lifecycle
259
+ ``open -> surfaced -> (resolved, removed here + logged)``."""
260
+ return memory_dir(vault) / "recommendations-open.jsonl"
261
+
262
+
263
+ def recommendations_log_path(vault: str | os.PathLike[str] | None = None) -> Path:
264
+ """Resolved-recommendations log (MEM-03) — append-only Markdown, one dated
265
+ entry per closed recommendation."""
266
+ return memory_dir(vault) / "recommendations-log.md"
267
+
268
+
269
+ def maintain_state_path(vault: str | os.PathLike[str] | None = None) -> Path:
270
+ """Per-branch ``brain maintain`` state (ADR-0003 Ruling 5/d) — ONE file
271
+ serving both the catch-up last-run markers and the heartbeat (last
272
+ attempt/status/consecutive-failures per branch). Read by
273
+ ``.claude/hooks/session-start.sh`` for the stale-nightly warning."""
274
+ override = os.environ.get("BRAIN_MAINTAIN_STATE")
275
+ if override:
276
+ return Path(override).expanduser()
277
+ return brain_runtime_dir(vault) / "maintain-state.json"
278
+
279
+
280
+ def maintain_lock_path(vault: str | os.PathLike[str] | None = None) -> Path:
281
+ """Single-runner lock for ``brain maintain`` (HARDENED:codex) — a second
282
+ concurrent run skips with a logged notice instead of racing the first."""
283
+ return brain_runtime_dir(vault) / "maintain.lock"
284
+
285
+
286
+ def graph_dir(vault: str | os.PathLike[str] | None = None) -> Path:
287
+ """GRF-01 discovery-graph runtime artifacts (ADR-0003 Ruling 6/(a)) —
288
+ gitignored, host-only, never published into the VM snapshot. Holds the
289
+ published ``graph.json`` + its corpus-drift ``manifest.json``, and (only on
290
+ a failed/partial build) a ``BUILD_FAILED.json`` marker written to a path
291
+ SEPARATE from the consumable ``graph.json`` (HARDENED:codex)."""
292
+ return brain_runtime_dir(vault) / "graph"
293
+
294
+
295
+ def graph_manifest_path(vault: str | os.PathLike[str] | None = None) -> Path:
296
+ """Per-note content-hash manifest the drift gate compares against."""
297
+ return graph_dir(vault) / "manifest.json"
298
+
299
+
300
+ def graph_json_path(vault: str | os.PathLike[str] | None = None) -> Path:
301
+ """The published, non-authoritative discovery graph."""
302
+ return graph_dir(vault) / "graph.json"
303
+
304
+
305
+ def graph_build_failed_marker_path(vault: str | os.PathLike[str] | None = None) -> Path:
306
+ """A failed/partial build's marker — NEVER the consumable ``graph.json``
307
+ path, so a partial build can never be mistaken for a valid publish."""
308
+ return graph_dir(vault) / "BUILD_FAILED.json"
309
+
310
+
311
+ def _health_history_root(vault: str | os.PathLike[str] | None = None) -> Path:
312
+ """Directory holding ``health-history.jsonl`` + its lock + archive
313
+ segments. Honors ``$BRAIN_HEALTH_HISTORY`` (the override's PARENT dir
314
+ becomes this root) so the lock and archive dir never drift from the
315
+ actual history file location — fix for review finding [5]: they used to
316
+ always resolve under ``brain_runtime_dir`` even when the history file
317
+ itself was overridden elsewhere (e.g. in tests), so a test pointing
318
+ ``$BRAIN_HEALTH_HISTORY`` at a tmp path still rotated/locked against the
319
+ real vault's ``.brain/`` dir."""
320
+ override = os.environ.get("BRAIN_HEALTH_HISTORY")
321
+ if override:
322
+ return Path(override).expanduser().parent
323
+ return brain_runtime_dir(vault)
324
+
325
+
326
+ def health_history_path(vault: str | os.PathLike[str] | None = None) -> Path:
327
+ """OBS-01 per-run health-metrics JSONL — one record per ``maintain`` run.
328
+ Rotated at ~1MB into ``health_archive_dir``."""
329
+ override = os.environ.get("BRAIN_HEALTH_HISTORY")
330
+ if override:
331
+ return Path(override).expanduser()
332
+ return brain_runtime_dir(vault) / "health-history.jsonl"
333
+
334
+
335
+ def health_history_lock_path(vault: str | os.PathLike[str] | None = None) -> Path:
336
+ """Dedicated short-lived exclusive lock serializing append+rotation
337
+ (OBS-01 correction 3) — separate from ``maintain_lock_path`` because a
338
+ stale/broken maintain lock must never also jam health-history writes."""
339
+ return _health_history_root(vault) / "health-history.lock"
340
+
341
+
342
+ def health_archive_dir(vault: str | os.PathLike[str] | None = None) -> Path:
343
+ """Rotated health-history segments — ``.brain/archive/health-history-*.jsonl``
344
+ (or alongside an overridden ``$BRAIN_HEALTH_HISTORY`` file's own dir)."""
345
+ return _health_history_root(vault) / "archive"
346
+
347
+
348
+ def health_sparse_path(vault: str | os.PathLike[str] | None = None) -> Path:
349
+ """Never-rotated sidecar holding ONLY the sparse health metrics
350
+ (``golden_score``, ``synthesis_cost_usd``) whenever they are non-null —
351
+ review finding [7]. The main ``health-history.jsonl`` read is bounded to a
352
+ ~14-day window (fix [6]) which would silently truncate a sparse metric's
353
+ prior observation older than that (golden scores land on a >quarterly
354
+ cadence), disabling its regression check. This sidecar grows ~one line per
355
+ week and is trivially small forever, so it is never windowed or rotated —
356
+ ``health_trend`` reads it in full for the sparse comparisons."""
357
+ return _health_history_root(vault) / "health-sparse.jsonl"
358
+
359
+
360
+ def anchor_dir() -> Path | None:
361
+ """Off-host audit-chain anchor dir (SEC-03), if configured.
362
+
363
+ No path lives under the vault by default (anchoring INTO the vault buys
364
+ nothing — see brain.anchor). ``None`` means no anchor is configured; the
365
+ scheduled `integrity`/`maintain` check then has no truncation guarantee
366
+ to fold in and says so explicitly (M-2)."""
367
+ override = os.environ.get("BRAIN_ANCHOR_DIR")
368
+ return Path(override).expanduser() if override else None