superlocalmemory 4.0.1 → 4.0.3

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 (86) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +37 -43
  3. package/package.json +1 -1
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/CLAUDE.md +3 -3
  6. package/plugin/agents/slm-governance-advisor.md +1 -1
  7. package/plugin/agents/slm-loop-runner.md +1 -1
  8. package/plugin/agents/slm-memory-advisor.md +1 -1
  9. package/plugin/agents/slm-optimize-advisor.md +1 -1
  10. package/plugin/requirements.txt +1 -1
  11. package/plugin/skills/slm-cache/SKILL.md +1 -1
  12. package/plugin/skills/slm-compress/SKILL.md +1 -1
  13. package/plugin/skills/slm-governance/SKILL.md +1 -1
  14. package/plugin/skills/slm-graph/SKILL.md +1 -1
  15. package/plugin/skills/slm-loop/SKILL.md +1 -1
  16. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  17. package/plugin/skills/slm-profile/SKILL.md +4 -4
  18. package/plugin/skills/slm-recall/SKILL.md +1 -1
  19. package/plugin/skills/slm-remember/SKILL.md +1 -1
  20. package/plugin/skills/slm-scope/SKILL.md +1 -1
  21. package/plugin/skills/slm-session/SKILL.md +1 -1
  22. package/plugin/skills/slm-status/SKILL.md +1 -1
  23. package/plugin-src/rules/AGENTS.md +3 -3
  24. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  25. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-governance/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-loop/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-profile/SKILL.md +4 -4
  31. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  32. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  33. package/plugin-src/skills/slm-scope/SKILL.md +1 -1
  34. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  36. package/pyproject.toml +3 -2
  37. package/scripts/postinstall-interactive.js +1 -0
  38. package/scripts/postinstall.js +4 -0
  39. package/src/superlocalmemory/__init__.py +1 -1
  40. package/src/superlocalmemory/cli/commands.py +70 -20
  41. package/src/superlocalmemory/cli/host_upgrades.py +175 -0
  42. package/src/superlocalmemory/cli/main.py +53 -5
  43. package/src/superlocalmemory/compliance/gdpr.py +104 -73
  44. package/src/superlocalmemory/contracts/__init__.py +1 -0
  45. package/src/superlocalmemory/contracts/schemas/agent-experience-v1.schema.json +92 -0
  46. package/src/superlocalmemory/contracts/schemas/agent-integration-contract-v2.schema.json +46 -0
  47. package/src/superlocalmemory/contracts/schemas/cognitive-turn-receipt-v1.schema.json +59 -0
  48. package/src/superlocalmemory/contracts/v402.py +62 -0
  49. package/src/superlocalmemory/core/engine.py +10 -0
  50. package/src/superlocalmemory/core/recall_pipeline.py +6 -0
  51. package/src/superlocalmemory/core/recall_worker.py +12 -0
  52. package/src/superlocalmemory/core/worker_pool.py +12 -0
  53. package/src/superlocalmemory/hooks/hook_handlers.py +16 -0
  54. package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +12 -6
  55. package/src/superlocalmemory/hooks/session_registry.py +136 -3
  56. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -2
  57. package/src/superlocalmemory/integrations/__init__.py +1 -0
  58. package/src/superlocalmemory/integrations/bounded_loops_v051.py +236 -0
  59. package/src/superlocalmemory/learning/database.py +21 -14
  60. package/src/superlocalmemory/mcp/_daemon_proxy.py +9 -0
  61. package/src/superlocalmemory/mcp/profiles.py +21 -12
  62. package/src/superlocalmemory/mcp/server.py +9 -6
  63. package/src/superlocalmemory/mcp/tools_brain.py +132 -0
  64. package/src/superlocalmemory/mcp/tools_core.py +25 -6
  65. package/src/superlocalmemory/mcp/tools_v3.py +16 -2
  66. package/src/superlocalmemory/retrieval/engine.py +43 -1
  67. package/src/superlocalmemory/retrieval/temporal_utils.py +16 -1
  68. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +151 -0
  69. package/src/superlocalmemory/server/routes/brain.py +206 -1
  70. package/src/superlocalmemory/server/routes/helpers.py +53 -35
  71. package/src/superlocalmemory/server/routes/v3_api.py +118 -11
  72. package/src/superlocalmemory/server/unified_daemon.py +25 -0
  73. package/src/superlocalmemory/storage/_migration_internals.py +4 -0
  74. package/src/superlocalmemory/storage/_schema_version.py +2 -2
  75. package/src/superlocalmemory/storage/agent_experience.py +490 -0
  76. package/src/superlocalmemory/storage/database.py +189 -34
  77. package/src/superlocalmemory/storage/migration_runner.py +8 -0
  78. package/src/superlocalmemory/storage/migrations/M015_add_pinned_column.py +18 -0
  79. package/src/superlocalmemory/storage/migrations/M040_agent_experience_receipts.py +254 -0
  80. package/src/superlocalmemory/storage/migrations/__init__.py +2 -0
  81. package/src/superlocalmemory/storage/schema.py +4 -0
  82. package/src/superlocalmemory/ui/js/auto-settings.js +18 -14
  83. package/src/superlocalmemory/ui/js/brain.js +57 -1
  84. package/src/superlocalmemory/ui/js/od-brain.js +114 -40
  85. package/src/superlocalmemory/ui/js/od-mcp.js +6 -6
  86. package/src/superlocalmemory/ui/js/od-settings.js +8 -1
@@ -321,25 +321,22 @@ def cmd_session(args: Namespace) -> None:
321
321
  print("Session close failed (daemon unreachable?)")
322
322
 
323
323
 
324
+ def _cmd_upgrade_hosts(args: Namespace) -> None:
325
+ """Run the consented host-integration refresh command."""
326
+ from superlocalmemory.cli.host_upgrades import cmd_upgrade_hosts
327
+
328
+ cmd_upgrade_hosts(args)
329
+
330
+
324
331
  def dispatch(args: Namespace) -> None:
325
332
  """Route CLI command to the appropriate handler."""
326
- # Auto-install/upgrade hooks on version change (single file read, ~0.1ms)
327
- if (
328
- args.command not in ("hooks", "codex", "init", "mcp")
329
- and not getattr(args, "dry_run", False)
330
- ):
331
- try:
332
- from superlocalmemory.hooks.claude_code_hooks import auto_install_if_needed
333
- auto_install_if_needed()
334
- except Exception:
335
- pass
336
-
337
333
  handlers = {
338
334
  "init": cmd_init,
339
335
  "setup": cmd_setup,
340
336
  "mode": cmd_mode,
341
337
  "provider": cmd_provider,
342
338
  "connect": cmd_connect,
339
+ "upgrade-hosts": _cmd_upgrade_hosts,
343
340
  "migrate": cmd_migrate,
344
341
  "list": cmd_list,
345
342
  "remember": cmd_remember,
@@ -349,6 +346,7 @@ def dispatch(args: Namespace) -> None:
349
346
  "delete": cmd_delete,
350
347
  "update": cmd_update,
351
348
  "status": cmd_status,
349
+ "brain": cmd_brain,
352
350
  "health": cmd_health,
353
351
  "doctor": cmd_doctor,
354
352
  "trace": cmd_trace,
@@ -1486,10 +1484,27 @@ def cmd_recall(args: Namespace) -> None:
1486
1484
  _sys.exit(1)
1487
1485
  _as_of = _as_of_norm
1488
1486
  as_of_qs = f"&as_of={quote(_as_of)}" if _as_of else ""
1487
+ def _strict_time_qs(attr: str, parameter: str) -> str:
1488
+ raw = getattr(args, attr, "") or ""
1489
+ if not raw:
1490
+ return ""
1491
+ from superlocalmemory.retrieval.temporal_utils import normalize_as_of
1492
+ normalized = normalize_as_of(raw)
1493
+ if normalized is None:
1494
+ import sys as _sys
1495
+ _sys.stderr.write(
1496
+ f"Error: invalid --{attr.replace('_', '-')} value: {raw!r}\n"
1497
+ )
1498
+ _sys.exit(1)
1499
+ return f"&{parameter}={quote(normalized)}"
1500
+ known_as_of_qs = _strict_time_qs("known_as_of", "known_as_of")
1501
+ valid_at_qs = _strict_time_qs("valid_at", "valid_at")
1502
+ unknown_qs = "&include_unknown=true" if getattr(args, "include_unknown", False) else ""
1489
1503
  result = daemon_request(
1490
1504
  "GET",
1491
1505
  f"/recall?q={quote(args.query)}&limit={args.limit}"
1492
- f"&session_id={quote(session_id)}{fast_qs}{scope_qs}{window_qs}{as_of_qs}",
1506
+ f"&session_id={quote(session_id)}{fast_qs}{scope_qs}{window_qs}{as_of_qs}"
1507
+ f"{known_as_of_qs}{valid_at_qs}{unknown_qs}",
1493
1508
  )
1494
1509
  if result and "results" in result:
1495
1510
  # Format daemon response same as engine response
@@ -2133,6 +2148,7 @@ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
2133
2148
  ("mode", "Switch memory mode: a (local) / b (Ollama) / c (cloud)"),
2134
2149
  ("provider", "Configure the cloud LLM provider + API key (Mode C)"),
2135
2150
  ("connect", "Auto-configure detected IDEs (Cursor, VS Code, …)"),
2151
+ ("upgrade-hosts", "Preview or explicitly refresh existing SLM host integrations"),
2136
2152
  ("hooks", "Install/inspect Claude Code hooks"),
2137
2153
  ("codex", "Configure the Codex / OpenAI integration"),
2138
2154
  ]),
@@ -2188,6 +2204,7 @@ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
2188
2204
  ("Learning & maintenance", [
2189
2205
  ("evolve", "Skill-evolution controls"),
2190
2206
  ("observe", "External observation / telemetry ingestion"),
2207
+ ("brain", "Show profile-scoped Living Brain evidence"),
2191
2208
  ("decay", "Run a forgetting/decay pass"),
2192
2209
  ("consolidate", "Merge/consolidate related memories"),
2193
2210
  ("quantize", "Quantize embeddings to save space"),
@@ -2949,14 +2966,6 @@ def cmd_mcp(_args: Namespace) -> None:
2949
2966
  except Exception:
2950
2967
  pass # A diagnostic must never prevent the server from starting.
2951
2968
 
2952
- # Auto-install hooks on MCP startup (fast path: ~0.1ms if already current)
2953
- # CRITICAL: No stdout — MCP uses stdio transport, any print corrupts protocol
2954
- try:
2955
- from superlocalmemory.hooks.claude_code_hooks import auto_install_if_needed
2956
- auto_install_if_needed()
2957
- except Exception:
2958
- pass
2959
-
2960
2969
  from superlocalmemory.mcp.server import server
2961
2970
 
2962
2971
  server.run(transport="stdio")
@@ -3680,6 +3689,47 @@ def cmd_session_context(args: Namespace) -> None:
3680
3689
  logger.debug("session-context (fast) failed: %s", exc)
3681
3690
 
3682
3691
 
3692
+ def cmd_brain(args: Namespace) -> None:
3693
+ """Read the portable, profile-scoped Agent Experience evidence summary.
3694
+
3695
+ This command deliberately opens no memory engine and starts no daemon. It
3696
+ is an indexed ``learning.db`` read, so support scripts and non-technical
3697
+ users can inspect the Living Brain without affecting recall latency.
3698
+ """
3699
+ from superlocalmemory.core.config import SLMConfig
3700
+ from superlocalmemory.infra.data_root import state_path
3701
+ from superlocalmemory.storage.agent_experience import get_profile_receipt_summary
3702
+
3703
+ config = SLMConfig.load()
3704
+ profile_id = config.active_profile
3705
+ data = {
3706
+ "profile_id": profile_id,
3707
+ "agent_experience": get_profile_receipt_summary(
3708
+ state_path("learning.db"), profile_id
3709
+ ),
3710
+ "control_plane": "observation_only",
3711
+ }
3712
+ if getattr(args, "json", False):
3713
+ from superlocalmemory.cli.json_output import json_print
3714
+
3715
+ json_print("brain", data=data, next_actions=[
3716
+ {"command": "slm dashboard", "description": "Open the Living Brain dashboard"},
3717
+ ])
3718
+ return
3719
+ evidence = data["agent_experience"]
3720
+ print("SuperLocalMemory Living Brain")
3721
+ print(f" Profile: {profile_id}")
3722
+ print(f" Agent experiences: {evidence['experiences_total']}")
3723
+ print(f" Claimed evidence authority: {evidence['claimed_evidence_experiences']}")
3724
+ print(f" Cognitive turns: {evidence['turns_total']}")
3725
+ if evidence["turns_by_state"]:
3726
+ states = ", ".join(
3727
+ f"{state}: {count}" for state, count in sorted(evidence["turns_by_state"].items())
3728
+ )
3729
+ print(f" Turn states: {states}")
3730
+ print(" Retrieval control: observation only")
3731
+
3732
+
3683
3733
  def cmd_observe(args: Namespace) -> None:
3684
3734
  """Evaluate and auto-capture content from stdin or argument.
3685
3735
 
@@ -0,0 +1,175 @@
1
+ """Consented, non-destructive upgrades for existing host integrations.
2
+
3
+ Package installation owns the SLM executable. This module owns the separate
4
+ operator-approved step that refreshes SLM-owned integration assets. It never
5
+ discovers a host and edits it implicitly: preview is the default and apply
6
+ requires either explicit hosts or an explicit all-detected acknowledgement.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from argparse import Namespace
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+
16
+ def validate_upgrade_request(*, apply: bool, hosts: list[str], all_detected: bool) -> None:
17
+ """Reject unbounded mutations before any host filesystem access."""
18
+ if apply and not hosts and not all_detected:
19
+ raise ValueError("--apply requires at least one --host or --all-detected")
20
+ if hosts and all_detected:
21
+ raise ValueError("choose explicit --host values or --all-detected, not both")
22
+
23
+
24
+ def cmd_upgrade_hosts(args: Namespace) -> None:
25
+ """Preview or explicitly apply host integration refreshes."""
26
+ hosts = list(getattr(args, "hosts", []) or [])
27
+ apply = bool(getattr(args, "apply", False))
28
+ all_detected = bool(getattr(args, "all_detected", False))
29
+ try:
30
+ validate_upgrade_request(apply=apply, hosts=hosts, all_detected=all_detected)
31
+ except ValueError as exc:
32
+ print(f"Error: {exc}")
33
+ return
34
+
35
+ from superlocalmemory.hooks.portable_kit import supported_ides
36
+
37
+ detected = set(_detected_hosts())
38
+ targets = sorted(detected) if all_detected else (hosts or sorted(detected))
39
+ managed_hosts = set(supported_ides()) | {"claude-code", "hermes"}
40
+ unknown = sorted(set(targets) - managed_hosts)
41
+ if unknown:
42
+ print(f"Error: unsupported host(s): {', '.join(unknown)}")
43
+ return
44
+ if not targets:
45
+ print("No existing SLM host integrations were detected.")
46
+ print("Run `slm setup` for first-time setup, or use `--host <name>` to target one host.")
47
+ return
48
+
49
+ operation = "Applying" if apply else "Previewing"
50
+ print(f"{operation} SLM host upgrades: {', '.join(targets)}")
51
+ if not apply:
52
+ print("No host files will be changed. Re-run with --apply and explicit targets to proceed.")
53
+
54
+ for host in targets:
55
+ result = _upgrade_host(host, apply=apply, already_integrated=host in detected)
56
+ status = result.get("status", "error")
57
+ detail = result.get("detail", "")
58
+ print(f" [{status}] {host}: {detail}")
59
+
60
+ if apply:
61
+ print("Restart affected host applications, then run `slm doctor` to verify SLM itself.")
62
+
63
+
64
+ def _detected_hosts(home: Path | None = None) -> list[str]:
65
+ """Return only hosts already containing an SLM-owned integration block."""
66
+ from superlocalmemory.hooks.portable_kit import (
67
+ IDE_MATRIX,
68
+ _load_config,
69
+ _ParseError,
70
+ )
71
+
72
+ effective_home = home or Path.home()
73
+ detected: list[str] = []
74
+ for host, desc in IDE_MATRIX.items():
75
+ if not desc.fmt:
76
+ continue
77
+ path = effective_home / desc.mcp_path_global
78
+ try:
79
+ data = _load_config(path, desc.fmt)
80
+ except _ParseError:
81
+ continue
82
+ if _contains_slm_block(data, desc):
83
+ detected.append(host)
84
+
85
+ # Claude Code's plugin is host-managed. A local SLM hook installation is
86
+ # the durable evidence that its SLM integration exists; upgrading the
87
+ # plugin remains an explicit Claude Code marketplace action.
88
+ try:
89
+ from superlocalmemory.hooks.claude_code_hooks import check_status
90
+
91
+ if check_status().get("installed"):
92
+ detected.append("claude-code")
93
+ except Exception:
94
+ pass
95
+
96
+ # Hermes has a native YAML mapping rather than the portable-kit schema.
97
+ # Detect it for reporting, but never overwrite it with an invented format.
98
+ hermes_path = effective_home / ".hermes" / "config.yaml"
99
+ try:
100
+ hermes = _load_config(hermes_path, "yaml")
101
+ servers = hermes.get("mcp_servers", {})
102
+ if isinstance(servers, dict) and "superlocalmemory" in servers:
103
+ detected.append("hermes")
104
+ except _ParseError:
105
+ pass
106
+ return sorted(set(detected))
107
+
108
+
109
+ def _contains_slm_block(data: dict[str, Any], desc: Any) -> bool:
110
+ if desc.fmt == "yaml":
111
+ providers = data.get(desc.server_key, [])
112
+ return isinstance(providers, list) and any(
113
+ isinstance(item, dict)
114
+ and item.get("params", {}).get("serverName") == "superlocalmemory"
115
+ for item in providers
116
+ )
117
+ servers = data.get(desc.server_key, {})
118
+ return isinstance(servers, dict) and "superlocalmemory" in servers
119
+
120
+
121
+ def _upgrade_host(host: str, *, apply: bool, already_integrated: bool) -> dict[str, str]:
122
+ """Refresh assets without degrading a working, host-specific MCP block."""
123
+ if host == "claude-code":
124
+ return {
125
+ "status": "plugin-managed",
126
+ "detail": "run `claude plugin update superlocalmemory@qualixar` in Claude Code",
127
+ }
128
+
129
+ if host == "hermes":
130
+ if already_integrated:
131
+ return {
132
+ "status": "verified",
133
+ "detail": "existing native Hermes SLM mapping preserved",
134
+ }
135
+ return {
136
+ "status": "blocked",
137
+ "detail": "Hermes is not connected; follow docs/ide-setup.md before retrying",
138
+ }
139
+
140
+ if host == "codex" and already_integrated:
141
+ from superlocalmemory.hooks.codex_assets import install_assets
142
+ from superlocalmemory.hooks.codex_hooks import install_hooks
143
+
144
+ assets, hooks = install_assets(dry_run=not apply), install_hooks(dry_run=not apply)
145
+ if not assets.get("success") or not hooks.get("success"):
146
+ return {"status": "blocked", "detail": "could not refresh SLM-owned Codex assets"}
147
+ action = "would refresh" if not apply else "refreshed"
148
+ return {
149
+ "status": "updated" if apply else "preview",
150
+ "detail": (
151
+ f"{action} SLM-owned Codex skills, agents, and hooks; "
152
+ "MCP block preserved"
153
+ ),
154
+ }
155
+
156
+ if already_integrated:
157
+ return {
158
+ "status": "verified",
159
+ "detail": "existing SLM MCP block preserved; no portable host assets require refresh",
160
+ }
161
+
162
+ # An explicitly named, not-yet-connected host is a first-time connection.
163
+ # This branch is never reached by --all-detected.
164
+ from superlocalmemory.hooks.portable_kit import connect_ide
165
+
166
+ result = connect_ide(host, dry_run=not apply)
167
+ if result.get("error"):
168
+ return {"status": "blocked", "detail": str(result["error"])}
169
+ action = result.get("mcp_config", "unknown")
170
+ if not apply:
171
+ return {"status": "preview", "detail": f"would refresh SLM MCP block ({action})"}
172
+ return {"status": "updated", "detail": f"SLM MCP block {action}"}
173
+
174
+
175
+ __all__ = ("cmd_upgrade_hosts", "validate_upgrade_request")
@@ -64,17 +64,19 @@ examples:
64
64
  documentation:
65
65
  Website: https://superlocalmemory.com
66
66
  GitHub: https://github.com/qualixar/superlocalmemory
67
- Paper: https://arxiv.org/abs/2603.14588
67
+ Paper: https://arxiv.org/abs/2608.08253
68
68
  """
69
69
 
70
70
 
71
71
  _NO_DAEMON_COMMANDS = {
72
- "setup", "mode", "provider", "connect", "migrate", "mcp", "warmup", "hooks", "codex",
72
+ "setup", "mode", "provider", "connect", "upgrade-hosts", "migrate", "mcp", "warmup", "hooks", "codex",
73
73
  "config", "evolve", "db",
74
74
  # v3.4.22 escape hatches — never auto-start the daemon on these.
75
75
  "disable", "enable", "clear-cache", "reconfigure", "benchmark",
76
76
  "rotate-token",
77
77
  "evidence",
78
+ # v4.0.2 receipt summary is a direct read-only learning.db query.
79
+ "brain",
78
80
  "diagnostics",
79
81
  # LLD-06 — agents launched through wrap start the daemon on demand.
80
82
  "wrap",
@@ -121,12 +123,17 @@ def main() -> None:
121
123
  and sys.argv[1] == "connect"
122
124
  and "--dry-run" in sys.argv[2:]
123
125
  )
126
+ _is_host_upgrade_preview = (
127
+ len(sys.argv) >= 2
128
+ and sys.argv[1] == "upgrade-hosts"
129
+ and "--apply" not in sys.argv[2:]
130
+ )
124
131
 
125
132
  # WP-07: lazy first-run init — runs after hook/mcp fast-paths so stdout
126
133
  # is never polluted on those paths (CRIT-3, MCP JSON-RPC purity).
127
134
  # Guarded: any failure must not crash the CLI (AC4).
128
135
  _is_mcp_cmd = len(sys.argv) >= 2 and sys.argv[1] == "mcp"
129
- if not _is_mcp_cmd and not _is_metadata_cmd and not _is_connect_dry_run:
136
+ if not _is_mcp_cmd and not _is_metadata_cmd and not _is_connect_dry_run and not _is_host_upgrade_preview:
130
137
  try:
131
138
  from superlocalmemory.cli._lazy_init import _ensure_initialized
132
139
  _ensure_initialized()
@@ -146,7 +153,7 @@ def main() -> None:
146
153
 
147
154
  # One-time post-upgrade banner — silent for fresh installs and
148
155
  # same-version runs. Guarded against I/O errors internally.
149
- if not _is_mcp_stdio and not _is_metadata_cmd and not _is_connect_dry_run:
156
+ if not _is_mcp_stdio and not _is_metadata_cmd and not _is_connect_dry_run and not _is_host_upgrade_preview:
150
157
  from superlocalmemory.cli.version_banner import check_and_emit_upgrade_banner
151
158
  if check_and_emit_upgrade_banner(_ver):
152
159
  # First post-upgrade invocation: apply the data-dir migration if
@@ -283,6 +290,23 @@ def main() -> None:
283
290
  ),
284
291
  )
285
292
 
293
+ upgrade_p = sub.add_parser(
294
+ "upgrade-hosts",
295
+ help="Preview or explicitly refresh installed SLM host integrations",
296
+ )
297
+ upgrade_p.add_argument(
298
+ "--host", action="append", dest="hosts", default=[],
299
+ help="Explicit host to refresh (repeatable; for example: codex, cursor)",
300
+ )
301
+ upgrade_p.add_argument(
302
+ "--all-detected", action="store_true", default=False,
303
+ help="Target only hosts that already contain an SLM integration",
304
+ )
305
+ upgrade_p.add_argument(
306
+ "--apply", action="store_true", default=False,
307
+ help="Apply the previewed changes; default is read-only preview",
308
+ )
309
+
286
310
  migrate_p = sub.add_parser("migrate", help="Migrate data from V2 to V3 schema")
287
311
  migrate_p.add_argument(
288
312
  "--rollback", action="store_true", help="Rollback migration",
@@ -394,6 +418,18 @@ def main() -> None:
394
418
  "(2026-01-01T00:00:00+00:00) that pins retrieval to a temporal "
395
419
  "snapshot. Default: current-state recall.",
396
420
  )
421
+ recall_p.add_argument(
422
+ "--known-as-of", dest="known_as_of", default="",
423
+ help="Strict transaction-time boundary: return only facts SLM knew by this ISO-8601 time.",
424
+ )
425
+ recall_p.add_argument(
426
+ "--valid-at", dest="valid_at", default="",
427
+ help="Strict event-time boundary: return only facts valid at this ISO-8601 time.",
428
+ )
429
+ recall_p.add_argument(
430
+ "--include-unknown", action="store_true",
431
+ help="Include pre-4.0.2 facts with unknown temporal provenance in strict time-travel.",
432
+ )
397
433
  recall_p.add_argument(
398
434
  "--fast", action="store_true",
399
435
  help="Force-skip the internal agentic verification round (all six retrieval "
@@ -606,6 +642,15 @@ def main() -> None:
606
642
  obs_p = sub.add_parser("observe", help="Auto-capture content (pipe or argument)")
607
643
  obs_p.add_argument("content", nargs="?", default="", help="Content to evaluate")
608
644
 
645
+ brain_p = sub.add_parser(
646
+ "brain", help="Show the local, profile-scoped Living Brain evidence summary"
647
+ )
648
+ brain_p.add_argument(
649
+ "action", nargs="?", default="status", choices=["status"],
650
+ help="Read-only Brain action (default: status)",
651
+ )
652
+ brain_p.add_argument("--json", action="store_true", help="Output structured JSON")
653
+
609
654
  # -- V3.3 Commands -------------------------------------------------
610
655
  decay_p = sub.add_parser("decay", help="Run Ebbinghaus forgetting decay cycle")
611
656
  decay_p.add_argument(
@@ -943,7 +988,10 @@ def main() -> None:
943
988
  sys.exit(0)
944
989
 
945
990
  # V3.3.19: Auto-trigger setup wizard on first use
946
- if not (args.command == "connect" and getattr(args, "dry_run", False)):
991
+ if not (
992
+ (args.command == "connect" and getattr(args, "dry_run", False))
993
+ or (args.command == "upgrade-hosts" and not getattr(args, "apply", False))
994
+ ):
947
995
  from superlocalmemory.cli.setup_wizard import check_first_use
948
996
  check_first_use(args.command)
949
997