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.
- package/CHANGELOG.md +50 -0
- package/README.md +37 -43
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +4 -4
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +3 -3
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +1 -1
- package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
- package/plugin-src/skills/slm-profile/SKILL.md +4 -4
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +3 -2
- package/scripts/postinstall-interactive.js +1 -0
- package/scripts/postinstall.js +4 -0
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +70 -20
- package/src/superlocalmemory/cli/host_upgrades.py +175 -0
- package/src/superlocalmemory/cli/main.py +53 -5
- package/src/superlocalmemory/compliance/gdpr.py +104 -73
- package/src/superlocalmemory/contracts/__init__.py +1 -0
- package/src/superlocalmemory/contracts/schemas/agent-experience-v1.schema.json +92 -0
- package/src/superlocalmemory/contracts/schemas/agent-integration-contract-v2.schema.json +46 -0
- package/src/superlocalmemory/contracts/schemas/cognitive-turn-receipt-v1.schema.json +59 -0
- package/src/superlocalmemory/contracts/v402.py +62 -0
- package/src/superlocalmemory/core/engine.py +10 -0
- package/src/superlocalmemory/core/recall_pipeline.py +6 -0
- package/src/superlocalmemory/core/recall_worker.py +12 -0
- package/src/superlocalmemory/core/worker_pool.py +12 -0
- package/src/superlocalmemory/hooks/hook_handlers.py +16 -0
- package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +12 -6
- package/src/superlocalmemory/hooks/session_registry.py +136 -3
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -2
- package/src/superlocalmemory/integrations/__init__.py +1 -0
- package/src/superlocalmemory/integrations/bounded_loops_v051.py +236 -0
- package/src/superlocalmemory/learning/database.py +21 -14
- package/src/superlocalmemory/mcp/_daemon_proxy.py +9 -0
- package/src/superlocalmemory/mcp/profiles.py +21 -12
- package/src/superlocalmemory/mcp/server.py +9 -6
- package/src/superlocalmemory/mcp/tools_brain.py +132 -0
- package/src/superlocalmemory/mcp/tools_core.py +25 -6
- package/src/superlocalmemory/mcp/tools_v3.py +16 -2
- package/src/superlocalmemory/retrieval/engine.py +43 -1
- package/src/superlocalmemory/retrieval/temporal_utils.py +16 -1
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +151 -0
- package/src/superlocalmemory/server/routes/brain.py +206 -1
- package/src/superlocalmemory/server/routes/helpers.py +53 -35
- package/src/superlocalmemory/server/routes/v3_api.py +118 -11
- package/src/superlocalmemory/server/unified_daemon.py +25 -0
- package/src/superlocalmemory/storage/_migration_internals.py +4 -0
- package/src/superlocalmemory/storage/_schema_version.py +2 -2
- package/src/superlocalmemory/storage/agent_experience.py +490 -0
- package/src/superlocalmemory/storage/database.py +189 -34
- package/src/superlocalmemory/storage/migration_runner.py +8 -0
- package/src/superlocalmemory/storage/migrations/M015_add_pinned_column.py +18 -0
- package/src/superlocalmemory/storage/migrations/M040_agent_experience_receipts.py +254 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +2 -0
- package/src/superlocalmemory/storage/schema.py +4 -0
- package/src/superlocalmemory/ui/js/auto-settings.js +18 -14
- package/src/superlocalmemory/ui/js/brain.js +57 -1
- package/src/superlocalmemory/ui/js/od-brain.js +114 -40
- package/src/superlocalmemory/ui/js/od-mcp.js +6 -6
- 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/
|
|
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 (
|
|
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
|
|