superlocalmemory 3.4.19 → 3.4.22
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 +24 -0
- package/README.md +42 -34
- package/bin/slm +11 -0
- package/bin/slm.bat +12 -0
- package/package.json +4 -3
- package/pyproject.toml +4 -3
- package/scripts/build-slm-hook.ps1 +40 -0
- package/scripts/build-slm-hook.sh +45 -0
- package/scripts/build_entry.py +452 -0
- package/scripts/ci/stage5b_gate.sh +50 -0
- package/scripts/postinstall/validation.js +187 -0
- package/scripts/postinstall-interactive.js +756 -0
- package/scripts/postinstall_binary.js +287 -0
- package/scripts/release_manifest.py +273 -0
- package/scripts/slm-hook.spec +56 -0
- package/skills/slm-build-graph/SKILL.md +423 -0
- package/skills/slm-list-recent/SKILL.md +348 -0
- package/skills/slm-recall/SKILL.md +343 -0
- package/skills/slm-remember/SKILL.md +194 -0
- package/skills/slm-show-patterns/SKILL.md +224 -0
- package/skills/slm-status/SKILL.md +363 -0
- package/skills/slm-switch-profile/SKILL.md +442 -0
- package/src/superlocalmemory/cli/commands.py +254 -79
- package/src/superlocalmemory/cli/context_commands.py +192 -0
- package/src/superlocalmemory/cli/daemon.py +15 -1
- package/src/superlocalmemory/cli/db_migrate.py +80 -0
- package/src/superlocalmemory/cli/escape_hatch.py +220 -0
- package/src/superlocalmemory/cli/main.py +72 -1
- package/src/superlocalmemory/core/context_cache.py +397 -0
- package/src/superlocalmemory/core/engine.py +38 -2
- package/src/superlocalmemory/core/engine_wiring.py +1 -1
- package/src/superlocalmemory/core/ram_lock.py +111 -0
- package/src/superlocalmemory/core/recall_pipeline.py +433 -3
- package/src/superlocalmemory/core/recall_worker.py +8 -3
- package/src/superlocalmemory/core/security_primitives.py +635 -0
- package/src/superlocalmemory/core/shadow_router.py +319 -0
- package/src/superlocalmemory/core/slm_disabled.py +87 -0
- package/src/superlocalmemory/core/slmignore.py +125 -0
- package/src/superlocalmemory/core/topic_signature.py +143 -0
- package/src/superlocalmemory/core/worker_pool.py +14 -3
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +2 -2
- package/src/superlocalmemory/evolution/budget.py +321 -0
- package/src/superlocalmemory/evolution/llm_dispatch.py +508 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +144 -94
- package/src/superlocalmemory/hooks/_outcome_common.py +506 -0
- package/src/superlocalmemory/hooks/adapter_base.py +317 -0
- package/src/superlocalmemory/hooks/antigravity_adapter.py +192 -0
- package/src/superlocalmemory/hooks/claude_code_hooks.py +33 -1
- package/src/superlocalmemory/hooks/context_payload.py +312 -0
- package/src/superlocalmemory/hooks/copilot_adapter.py +154 -0
- package/src/superlocalmemory/hooks/cross_platform_connector.py +90 -0
- package/src/superlocalmemory/hooks/cursor_adapter.py +195 -0
- package/src/superlocalmemory/hooks/hook_handlers.py +109 -8
- package/src/superlocalmemory/hooks/ide_connector.py +25 -2
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +165 -0
- package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +223 -0
- package/src/superlocalmemory/hooks/prewarm_auth.py +170 -0
- package/src/superlocalmemory/hooks/session_registry.py +186 -0
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +134 -0
- package/src/superlocalmemory/hooks/sync_loop.py +114 -0
- package/src/superlocalmemory/hooks/user_prompt_hook.py +128 -0
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +202 -0
- package/src/superlocalmemory/infra/backup.py +3 -3
- package/src/superlocalmemory/infra/cloud_backup.py +2 -2
- package/src/superlocalmemory/infra/event_bus.py +2 -2
- package/src/superlocalmemory/infra/webhook_dispatcher.py +3 -3
- package/src/superlocalmemory/learning/arm_catalog.py +99 -0
- package/src/superlocalmemory/learning/bandit.py +526 -0
- package/src/superlocalmemory/learning/bandit_cache.py +133 -0
- package/src/superlocalmemory/learning/behavioral.py +53 -1
- package/src/superlocalmemory/learning/consolidation_cycle.py +381 -0
- package/src/superlocalmemory/learning/consolidation_worker.py +188 -520
- package/src/superlocalmemory/learning/database.py +256 -0
- package/src/superlocalmemory/learning/dedup_hnsw.py +413 -0
- package/src/superlocalmemory/learning/ensemble.py +300 -0
- package/src/superlocalmemory/learning/fact_outcome_joins.py +207 -0
- package/src/superlocalmemory/learning/forgetting_scheduler.py +55 -0
- package/src/superlocalmemory/learning/hnsw_dedup.py +69 -0
- package/src/superlocalmemory/learning/labeler.py +87 -0
- package/src/superlocalmemory/learning/legacy_migration.py +277 -0
- package/src/superlocalmemory/learning/memory_merge.py +160 -0
- package/src/superlocalmemory/learning/model_cache.py +269 -0
- package/src/superlocalmemory/learning/model_rollback.py +278 -0
- package/src/superlocalmemory/learning/outcome_queue.py +284 -0
- package/src/superlocalmemory/learning/pattern_miner.py +415 -0
- package/src/superlocalmemory/learning/pattern_miner_constants.py +47 -0
- package/src/superlocalmemory/learning/ranker.py +225 -81
- package/src/superlocalmemory/learning/ranker_common.py +163 -0
- package/src/superlocalmemory/learning/ranker_retrain_legacy.py +202 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +411 -0
- package/src/superlocalmemory/learning/reward.py +777 -0
- package/src/superlocalmemory/learning/reward_archive.py +210 -0
- package/src/superlocalmemory/learning/reward_boost.py +201 -0
- package/src/superlocalmemory/learning/reward_proxy.py +326 -0
- package/src/superlocalmemory/learning/shadow_test.py +524 -0
- package/src/superlocalmemory/learning/signal_worker.py +270 -0
- package/src/superlocalmemory/learning/signals.py +314 -0
- package/src/superlocalmemory/learning/trigram_index.py +547 -0
- package/src/superlocalmemory/mcp/server.py +5 -5
- package/src/superlocalmemory/mcp/tools_context.py +183 -0
- package/src/superlocalmemory/mcp/tools_core.py +92 -27
- package/src/superlocalmemory/parameterization/soft_prompt_generator.py +13 -0
- package/src/superlocalmemory/retrieval/engine.py +52 -0
- package/src/superlocalmemory/server/api.py +2 -2
- package/src/superlocalmemory/server/bandit_loops.py +140 -0
- package/src/superlocalmemory/server/middleware/__init__.py +11 -0
- package/src/superlocalmemory/server/middleware/security_headers.py +144 -0
- package/src/superlocalmemory/server/routes/backup.py +36 -13
- package/src/superlocalmemory/server/routes/behavioral.py +50 -19
- package/src/superlocalmemory/server/routes/brain.py +1234 -0
- package/src/superlocalmemory/server/routes/data_io.py +4 -4
- package/src/superlocalmemory/server/routes/events.py +2 -2
- package/src/superlocalmemory/server/routes/helpers.py +1 -1
- package/src/superlocalmemory/server/routes/learning.py +192 -7
- package/src/superlocalmemory/server/routes/memories.py +189 -1
- package/src/superlocalmemory/server/routes/prewarm.py +171 -0
- package/src/superlocalmemory/server/routes/profiles.py +3 -3
- package/src/superlocalmemory/server/routes/token.py +88 -0
- package/src/superlocalmemory/server/routes/ws.py +5 -5
- package/src/superlocalmemory/server/security_middleware.py +13 -7
- package/src/superlocalmemory/server/ui.py +2 -2
- package/src/superlocalmemory/server/unified_daemon.py +335 -3
- package/src/superlocalmemory/skills/slm-build-graph/SKILL.md +423 -0
- package/src/superlocalmemory/skills/slm-list-recent/SKILL.md +348 -0
- package/src/superlocalmemory/skills/slm-recall/SKILL.md +343 -0
- package/src/superlocalmemory/skills/slm-remember/SKILL.md +194 -0
- package/src/superlocalmemory/skills/slm-show-patterns/SKILL.md +224 -0
- package/src/superlocalmemory/skills/slm-status/SKILL.md +363 -0
- package/src/superlocalmemory/skills/slm-switch-profile/SKILL.md +442 -0
- package/src/superlocalmemory/storage/migration_runner.py +545 -0
- package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +67 -0
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +132 -0
- package/src/superlocalmemory/storage/migrations/M003_migration_log.py +38 -0
- package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +46 -0
- package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +75 -0
- package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +75 -0
- package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +63 -0
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +75 -0
- package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +87 -0
- package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +72 -0
- package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +55 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +81 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/ui/css/brain.css +409 -0
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +645 -0
- package/src/superlocalmemory/ui/index.html +459 -1345
- package/src/superlocalmemory/ui/js/brain.js +1321 -0
- package/src/superlocalmemory/ui/js/clusters.js +123 -4
- package/src/superlocalmemory/ui/js/init.js +48 -39
- package/src/superlocalmemory/ui/js/memories.js +88 -2
- package/src/superlocalmemory/ui/js/modal.js +71 -1
- package/src/superlocalmemory/ui/js/ng-shell.js +101 -88
- package/src/superlocalmemory/ui/js/trust-dashboard.js +168 -25
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +2018 -0
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +7 -0
- package/src/superlocalmemory/ui/vendor/bootstrap.min.css +6 -0
- package/src/superlocalmemory/ui/vendor/d3.v7.min.js +2 -0
- package/src/superlocalmemory/ui/vendor/graphology-library.min.js +2 -0
- package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +2 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +8 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/sigma.min.js +1 -0
- package/src/superlocalmemory/ui/js/behavioral.js +0 -447
- package/src/superlocalmemory/ui/js/graph-core.js +0 -447
- package/src/superlocalmemory/ui/js/graph-interactions.js +0 -351
- package/src/superlocalmemory/ui/js/learning.js +0 -435
- package/src/superlocalmemory/ui/js/patterns.js +0 -93
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -647
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -335
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -58
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -12,10 +12,55 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
import os
|
|
15
16
|
import sys
|
|
16
17
|
from argparse import Namespace
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def _cmd_db_dispatch(args: Namespace) -> None:
|
|
21
|
+
"""Route ``slm db ...`` subcommands. LLD-06 §7.2."""
|
|
22
|
+
sub = getattr(args, "db_command", None)
|
|
23
|
+
if sub == "migrate":
|
|
24
|
+
from superlocalmemory.cli.db_migrate import cmd_db_migrate
|
|
25
|
+
rc = cmd_db_migrate(args)
|
|
26
|
+
if rc:
|
|
27
|
+
sys.exit(rc)
|
|
28
|
+
return
|
|
29
|
+
print("Usage: slm db migrate [--status] [--dry-run]")
|
|
30
|
+
sys.exit(2)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _cmd_escape_disable(args: Namespace) -> None:
|
|
34
|
+
from superlocalmemory.cli.escape_hatch import cmd_disable
|
|
35
|
+
cmd_disable(args)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _cmd_escape_enable(args: Namespace) -> None:
|
|
39
|
+
from superlocalmemory.cli.escape_hatch import cmd_enable
|
|
40
|
+
cmd_enable(args)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _cmd_escape_clear_cache(args: Namespace) -> None:
|
|
44
|
+
from superlocalmemory.cli.escape_hatch import cmd_clear_cache
|
|
45
|
+
cmd_clear_cache(args)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _cmd_escape_reconfigure(args: Namespace) -> None:
|
|
49
|
+
from superlocalmemory.cli.escape_hatch import cmd_reconfigure
|
|
50
|
+
cmd_reconfigure(args)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _cmd_escape_benchmark(args: Namespace) -> None:
|
|
54
|
+
from superlocalmemory.cli.escape_hatch import cmd_benchmark
|
|
55
|
+
cmd_benchmark(args)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _cmd_escape_rotate_token(args: Namespace) -> None:
|
|
59
|
+
"""S-M07: rotate the install token."""
|
|
60
|
+
from superlocalmemory.cli.escape_hatch import cmd_rotate_token
|
|
61
|
+
cmd_rotate_token(args)
|
|
62
|
+
|
|
63
|
+
|
|
19
64
|
def dispatch(args: Namespace) -> None:
|
|
20
65
|
"""Route CLI command to the appropriate handler."""
|
|
21
66
|
# Auto-install/upgrade hooks on version change (single file read, ~0.1ms)
|
|
@@ -67,6 +112,17 @@ def dispatch(args: Namespace) -> None:
|
|
|
67
112
|
# V3.4.11 skill evolution
|
|
68
113
|
"config": cmd_config,
|
|
69
114
|
"evolve": cmd_evolve,
|
|
115
|
+
# V3.4.22 LLD-05 context pre-staging
|
|
116
|
+
"context": _cmd_context_dispatch,
|
|
117
|
+
# V3.4.22 LLD-06 additive schema migrations
|
|
118
|
+
"db": _cmd_db_dispatch,
|
|
119
|
+
# V3.4.22 Stage 8 SB-5 — MASTER-PLAN §8 escape hatches.
|
|
120
|
+
"disable": _cmd_escape_disable,
|
|
121
|
+
"enable": _cmd_escape_enable,
|
|
122
|
+
"clear-cache": _cmd_escape_clear_cache,
|
|
123
|
+
"reconfigure": _cmd_escape_reconfigure,
|
|
124
|
+
"benchmark": _cmd_escape_benchmark,
|
|
125
|
+
"rotate-token": _cmd_escape_rotate_token,
|
|
70
126
|
}
|
|
71
127
|
handler = handlers.get(args.command)
|
|
72
128
|
if handler:
|
|
@@ -633,8 +689,21 @@ def cmd_provider(args: Namespace) -> None:
|
|
|
633
689
|
print(f"Model: {config.llm.model}")
|
|
634
690
|
|
|
635
691
|
|
|
692
|
+
def _cmd_context_dispatch(args: Namespace) -> None:
|
|
693
|
+
"""V3.4.22 LLD-05: ``slm context prestage``."""
|
|
694
|
+
from superlocalmemory.cli.context_commands import cmd_context
|
|
695
|
+
cmd_context(args)
|
|
696
|
+
|
|
697
|
+
|
|
636
698
|
def cmd_connect(args: Namespace) -> None:
|
|
637
|
-
"""Configure IDE integrations."""
|
|
699
|
+
"""Configure IDE integrations. V3.4.22: ``--cross-platform`` uses LLD-05."""
|
|
700
|
+
# Route --disable <name> and --cross-platform to the LLD-05 orchestrator.
|
|
701
|
+
if getattr(args, "disable", None) or getattr(args, "cross_platform", False):
|
|
702
|
+
from superlocalmemory.cli.context_commands import (
|
|
703
|
+
cmd_connect_cross_platform,
|
|
704
|
+
)
|
|
705
|
+
cmd_connect_cross_platform(args)
|
|
706
|
+
return
|
|
638
707
|
from superlocalmemory.hooks.ide_connector import IDEConnector
|
|
639
708
|
|
|
640
709
|
connector = IDEConnector()
|
|
@@ -824,12 +893,19 @@ def cmd_recall(args: Namespace) -> None:
|
|
|
824
893
|
|
|
825
894
|
# V3.3.21: Route through daemon for instant response (no cold start).
|
|
826
895
|
# Falls back to direct engine if daemon not running.
|
|
896
|
+
# S9-DASH-02: pass a stable session_id derived from the shell's
|
|
897
|
+
# parent PID so a sequence of CLI recalls in one terminal can be
|
|
898
|
+
# grouped. The Stop hook on session end won't fire for CLI, so
|
|
899
|
+
# these outcomes are closed by the reaper (TTL → neutral reward).
|
|
827
900
|
try:
|
|
828
901
|
from superlocalmemory.cli.daemon import is_daemon_running, daemon_request, ensure_daemon
|
|
829
902
|
if is_daemon_running() or ensure_daemon():
|
|
830
903
|
from urllib.parse import quote
|
|
904
|
+
session_id = f"cli:{os.getppid()}"
|
|
831
905
|
result = daemon_request(
|
|
832
|
-
"GET",
|
|
906
|
+
"GET",
|
|
907
|
+
f"/recall?q={quote(args.query)}&limit={args.limit}"
|
|
908
|
+
f"&session_id={quote(session_id)}",
|
|
833
909
|
)
|
|
834
910
|
if result and "results" in result:
|
|
835
911
|
# Format daemon response same as engine response
|
|
@@ -1156,6 +1232,30 @@ def cmd_status(args: Namespace) -> None:
|
|
|
1156
1232
|
size_mb = round(config.db_path.stat().st_size / 1024 / 1024, 2)
|
|
1157
1233
|
print(f" DB size: {size_mb} MB")
|
|
1158
1234
|
|
|
1235
|
+
# S9-UX-07 / S9-UX-13: --verbose surfaces the disabled marker,
|
|
1236
|
+
# last-version marker, and daemon port so users who are debugging
|
|
1237
|
+
# "slm seems broken" get the signal without opening three files.
|
|
1238
|
+
if getattr(args, "verbose", False):
|
|
1239
|
+
home = config.base_dir
|
|
1240
|
+
disabled_path = home / ".disabled"
|
|
1241
|
+
version_path = home / ".last_version"
|
|
1242
|
+
print()
|
|
1243
|
+
print(" --verbose --")
|
|
1244
|
+
print(
|
|
1245
|
+
f" Disabled marker: "
|
|
1246
|
+
f"{'YES (slm enable to reactivate)' if disabled_path.exists() else 'no'}",
|
|
1247
|
+
)
|
|
1248
|
+
if version_path.exists():
|
|
1249
|
+
try:
|
|
1250
|
+
last_ver = version_path.read_text(encoding="utf-8").strip()
|
|
1251
|
+
except OSError:
|
|
1252
|
+
last_ver = "(unreadable)"
|
|
1253
|
+
print(f" Last booted version: {last_ver}")
|
|
1254
|
+
else:
|
|
1255
|
+
print(" Last booted version: (never booted)")
|
|
1256
|
+
port = os.environ.get("SLM_DAEMON_PORT") or "8765"
|
|
1257
|
+
print(f" Daemon port: {port}")
|
|
1258
|
+
|
|
1159
1259
|
|
|
1160
1260
|
def cmd_health(args: Namespace) -> None:
|
|
1161
1261
|
"""Show math layer health status."""
|
|
@@ -1198,11 +1298,18 @@ def cmd_health(args: Namespace) -> None:
|
|
|
1198
1298
|
|
|
1199
1299
|
|
|
1200
1300
|
def cmd_doctor(args: Namespace) -> None:
|
|
1201
|
-
"""Comprehensive pre-flight check — verify everything works.
|
|
1301
|
+
"""Comprehensive pre-flight check — verify everything works.
|
|
1302
|
+
|
|
1303
|
+
S9-UX-10: ``--quick`` skips the slow probes (embedding worker
|
|
1304
|
+
subprocess, Ollama roundtrip) so first-run installer hooks can
|
|
1305
|
+
surface a sub-second PASS/FAIL line before letting the user go.
|
|
1306
|
+
Full doctor remains the default for ``slm doctor`` with no flag.
|
|
1307
|
+
"""
|
|
1202
1308
|
import shutil
|
|
1203
1309
|
from pathlib import Path
|
|
1204
1310
|
|
|
1205
1311
|
use_json = getattr(args, "json", False)
|
|
1312
|
+
quick = getattr(args, "quick", False)
|
|
1206
1313
|
checks: list[dict] = []
|
|
1207
1314
|
passed = warned = failed = 0
|
|
1208
1315
|
|
|
@@ -1315,86 +1422,119 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
1315
1422
|
_check("Performance deps", "WARN", f"Missing: {', '.join(missing)}",
|
|
1316
1423
|
"pip install diskcache orjson")
|
|
1317
1424
|
|
|
1318
|
-
# 7. Embedding worker functional test
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1425
|
+
# 7. Embedding worker functional test — skipped under --quick.
|
|
1426
|
+
if quick:
|
|
1427
|
+
_check("Embedding worker", "PASS", "skipped (--quick)")
|
|
1428
|
+
else:
|
|
1429
|
+
try:
|
|
1430
|
+
import subprocess as _sp
|
|
1431
|
+
import json as _json
|
|
1432
|
+
|
|
1433
|
+
env = {
|
|
1434
|
+
**__import__("os").environ,
|
|
1435
|
+
"CUDA_VISIBLE_DEVICES": "",
|
|
1436
|
+
"PYTORCH_MPS_HIGH_WATERMARK_RATIO": "0.0",
|
|
1437
|
+
"TOKENIZERS_PARALLELISM": "false",
|
|
1438
|
+
"TORCH_DEVICE": "cpu",
|
|
1439
|
+
}
|
|
1440
|
+
proc = _sp.Popen(
|
|
1441
|
+
[sys.executable, "-m",
|
|
1442
|
+
"superlocalmemory.core.embedding_worker"],
|
|
1443
|
+
stdin=_sp.PIPE, stdout=_sp.PIPE, stderr=_sp.DEVNULL,
|
|
1444
|
+
text=True, bufsize=1, env=env,
|
|
1445
|
+
)
|
|
1446
|
+
proc.stdin.write(_json.dumps({"cmd": "ping"}) + "\n")
|
|
1447
|
+
proc.stdin.flush()
|
|
1448
|
+
|
|
1449
|
+
import select as _sel
|
|
1450
|
+
ready, _, _ = _sel.select([proc.stdout], [], [], 30)
|
|
1451
|
+
if ready:
|
|
1452
|
+
resp = _json.loads(proc.stdout.readline())
|
|
1453
|
+
if resp.get("ok"):
|
|
1454
|
+
_check(
|
|
1455
|
+
"Embedding worker", "PASS",
|
|
1456
|
+
f"responsive (PID {proc.pid}, "
|
|
1457
|
+
f"Python {sys.executable})",
|
|
1458
|
+
)
|
|
1459
|
+
else:
|
|
1460
|
+
_check(
|
|
1461
|
+
"Embedding worker", "FAIL",
|
|
1462
|
+
f"error: {resp.get('error', 'unknown')}",
|
|
1463
|
+
"pip install sentence-transformers einops torch",
|
|
1464
|
+
)
|
|
1345
1465
|
else:
|
|
1346
|
-
_check("Embedding worker", "FAIL",
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
_check("Embedding worker", "FAIL", str(exc),
|
|
1360
|
-
"slm warmup")
|
|
1466
|
+
_check("Embedding worker", "FAIL", "timed out (30s)",
|
|
1467
|
+
"slm warmup")
|
|
1468
|
+
proc.stdin.write(_json.dumps({"cmd": "quit"}) + "\n")
|
|
1469
|
+
proc.stdin.flush()
|
|
1470
|
+
proc.wait(timeout=5)
|
|
1471
|
+
except FileNotFoundError:
|
|
1472
|
+
_check(
|
|
1473
|
+
"Embedding worker", "FAIL",
|
|
1474
|
+
"embedding_worker module not found",
|
|
1475
|
+
"Reinstall: npm install -g superlocalmemory",
|
|
1476
|
+
)
|
|
1477
|
+
except Exception as exc:
|
|
1478
|
+
_check("Embedding worker", "FAIL", str(exc), "slm warmup")
|
|
1361
1479
|
|
|
1362
|
-
# 8. Ollama connectivity (Mode B only)
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
import
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if
|
|
1376
|
-
|
|
1377
|
-
|
|
1480
|
+
# 8. Ollama connectivity (Mode B only) — skipped under --quick.
|
|
1481
|
+
if quick:
|
|
1482
|
+
_check("Ollama / API key", "PASS", "skipped (--quick)")
|
|
1483
|
+
else:
|
|
1484
|
+
try:
|
|
1485
|
+
from superlocalmemory.core.config import SLMConfig
|
|
1486
|
+
config = SLMConfig.load()
|
|
1487
|
+
if config.mode.value == "b":
|
|
1488
|
+
import httpx
|
|
1489
|
+
try:
|
|
1490
|
+
resp = httpx.get(
|
|
1491
|
+
f"{config.llm.api_base}/api/tags", timeout=5.0,
|
|
1492
|
+
)
|
|
1493
|
+
if resp.status_code == 200:
|
|
1494
|
+
models = [
|
|
1495
|
+
m["name"].split(":")[0]
|
|
1496
|
+
for m in resp.json().get("models", [])
|
|
1497
|
+
]
|
|
1498
|
+
has_llm = config.llm.model.split(":")[0] in models
|
|
1499
|
+
if has_llm:
|
|
1500
|
+
_check(
|
|
1501
|
+
"Ollama", "PASS",
|
|
1502
|
+
f"running, {len(models)} models, "
|
|
1503
|
+
f"'{config.llm.model}' available",
|
|
1504
|
+
)
|
|
1505
|
+
else:
|
|
1506
|
+
_check(
|
|
1507
|
+
"Ollama", "WARN",
|
|
1508
|
+
f"running but '{config.llm.model}' "
|
|
1509
|
+
f"not pulled",
|
|
1510
|
+
f"ollama pull {config.llm.model}",
|
|
1511
|
+
)
|
|
1378
1512
|
else:
|
|
1379
|
-
_check(
|
|
1380
|
-
|
|
1381
|
-
|
|
1513
|
+
_check(
|
|
1514
|
+
"Ollama", "WARN",
|
|
1515
|
+
f"HTTP {resp.status_code}",
|
|
1516
|
+
"brew services start ollama",
|
|
1517
|
+
)
|
|
1518
|
+
except Exception:
|
|
1519
|
+
_check(
|
|
1520
|
+
"Ollama", "WARN",
|
|
1521
|
+
"not reachable at " + config.llm.api_base,
|
|
1522
|
+
"brew services start ollama",
|
|
1523
|
+
)
|
|
1524
|
+
elif config.mode.value == "c":
|
|
1525
|
+
if config.llm.api_key:
|
|
1526
|
+
_check(
|
|
1527
|
+
"API key", "PASS",
|
|
1528
|
+
f"provider={config.llm.provider}, "
|
|
1529
|
+
f"key=***{config.llm.api_key[-4:]}",
|
|
1530
|
+
)
|
|
1382
1531
|
else:
|
|
1383
|
-
_check(
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
# Mode C — check API key
|
|
1390
|
-
if config.llm.api_key:
|
|
1391
|
-
_check("API key", "PASS",
|
|
1392
|
-
f"provider={config.llm.provider}, key=***{config.llm.api_key[-4:]}")
|
|
1393
|
-
else:
|
|
1394
|
-
_check("API key", "WARN", "no API key configured",
|
|
1395
|
-
"slm provider set")
|
|
1396
|
-
except Exception:
|
|
1397
|
-
pass # Config load failed — already caught above
|
|
1532
|
+
_check(
|
|
1533
|
+
"API key", "WARN", "no API key configured",
|
|
1534
|
+
"slm provider set",
|
|
1535
|
+
)
|
|
1536
|
+
except Exception:
|
|
1537
|
+
pass # Config load failed — already caught above
|
|
1398
1538
|
|
|
1399
1539
|
# 9. Disk space
|
|
1400
1540
|
slm_home = Path.home() / ".superlocalmemory"
|
|
@@ -1799,6 +1939,41 @@ def cmd_hooks(args: Namespace) -> None:
|
|
|
1799
1939
|
if include_gate:
|
|
1800
1940
|
print(" Gate: ON (enforces session_init — experimental)")
|
|
1801
1941
|
print(" SLM: Hooks installed into Claude Code (slm hooks remove to undo)")
|
|
1942
|
+
# S9-DASH-11: also install skills so /slm-recall, /slm-remember
|
|
1943
|
+
# etc. are available immediately in Claude Code regardless of
|
|
1944
|
+
# whether the user installed via npm or pip.
|
|
1945
|
+
try:
|
|
1946
|
+
import importlib.resources as _ir
|
|
1947
|
+
import importlib.util as _iu
|
|
1948
|
+
import shutil as _sh
|
|
1949
|
+
from pathlib import Path as _P
|
|
1950
|
+
|
|
1951
|
+
claude_skills_dir = _P.home() / ".claude" / "skills"
|
|
1952
|
+
claude_skills_dir.mkdir(parents=True, exist_ok=True)
|
|
1953
|
+
|
|
1954
|
+
# Try Python-package bundled skills first (works for both
|
|
1955
|
+
# pip and npm users who have the Python pkg installed).
|
|
1956
|
+
pkg_skills: _P | None = None
|
|
1957
|
+
spec = _iu.find_spec("superlocalmemory")
|
|
1958
|
+
if spec and spec.submodule_search_locations:
|
|
1959
|
+
candidate = _P(list(spec.submodule_search_locations)[0]) / "skills"
|
|
1960
|
+
if candidate.is_dir():
|
|
1961
|
+
pkg_skills = candidate
|
|
1962
|
+
|
|
1963
|
+
if pkg_skills:
|
|
1964
|
+
installed_skills = 0
|
|
1965
|
+
for d in pkg_skills.iterdir():
|
|
1966
|
+
if d.is_dir():
|
|
1967
|
+
src_skill = d / "SKILL.md"
|
|
1968
|
+
if src_skill.exists():
|
|
1969
|
+
dst = claude_skills_dir / (d.name + ".md")
|
|
1970
|
+
_sh.copy2(str(src_skill), str(dst))
|
|
1971
|
+
installed_skills += 1
|
|
1972
|
+
if installed_skills:
|
|
1973
|
+
print(f" Skills: {installed_skills} skills installed → {claude_skills_dir}")
|
|
1974
|
+
print(" Use /slm-recall, /slm-remember, /slm-status in Claude Code")
|
|
1975
|
+
except Exception as _skill_exc:
|
|
1976
|
+
pass # non-fatal — skills can be installed via install-skills.sh
|
|
1802
1977
|
else:
|
|
1803
1978
|
print(f"Installation failed: {result['errors']}")
|
|
1804
1979
|
elif action == "remove":
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory v3.4.22 — LLD-05 §8
|
|
4
|
+
|
|
5
|
+
"""CLI — ``slm context prestage`` and ``slm connect --disable`` (LLD-05).
|
|
6
|
+
|
|
7
|
+
Kept in a focused module (instead of bloating cli/commands.py past its cap).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json as _json
|
|
13
|
+
import os
|
|
14
|
+
from argparse import Namespace
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Callable
|
|
17
|
+
|
|
18
|
+
from superlocalmemory.hooks.antigravity_adapter import AntigravityAdapter
|
|
19
|
+
from superlocalmemory.hooks.copilot_adapter import CopilotAdapter
|
|
20
|
+
from superlocalmemory.hooks.cursor_adapter import CursorAdapter
|
|
21
|
+
from superlocalmemory.hooks.context_payload import (
|
|
22
|
+
RecallFn,
|
|
23
|
+
build_payload,
|
|
24
|
+
format_decisions,
|
|
25
|
+
format_entities,
|
|
26
|
+
format_memories,
|
|
27
|
+
format_topics,
|
|
28
|
+
)
|
|
29
|
+
from superlocalmemory.hooks.cross_platform_connector import (
|
|
30
|
+
CrossPlatformConnector,
|
|
31
|
+
)
|
|
32
|
+
from superlocalmemory.mcp.tools_context import prestage_context
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _noop_recall(_q: str, _limit: int, _profile: str) -> list[dict]:
|
|
36
|
+
"""Placeholder recall when the daemon/engine isn't reachable."""
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_recall_fn() -> RecallFn:
|
|
41
|
+
"""Wire the real recall engine if available, else no-op."""
|
|
42
|
+
try:
|
|
43
|
+
from superlocalmemory.core.engine import Engine # pragma: no cover
|
|
44
|
+
eng = Engine.get_shared()
|
|
45
|
+
def _fn(q: str, limit: int, profile_id: str) -> list[dict]:
|
|
46
|
+
try:
|
|
47
|
+
return eng.recall(q, limit=limit) or [] # type: ignore
|
|
48
|
+
except Exception:
|
|
49
|
+
return []
|
|
50
|
+
return _fn
|
|
51
|
+
except Exception:
|
|
52
|
+
return _noop_recall
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _default_sync_log_db() -> Path:
|
|
56
|
+
return Path(os.environ.get("SLM_MEMORY_DB",
|
|
57
|
+
str(Path.home() / ".superlocalmemory"
|
|
58
|
+
/ "memory.db")))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build_default_adapters(
|
|
62
|
+
*, base_dir: Path | None = None,
|
|
63
|
+
recall_fn: RecallFn | None = None,
|
|
64
|
+
sync_log_db: Path | None = None,
|
|
65
|
+
) -> list:
|
|
66
|
+
base = Path(base_dir or Path.cwd())
|
|
67
|
+
recall = recall_fn or _get_recall_fn()
|
|
68
|
+
db = sync_log_db or _default_sync_log_db()
|
|
69
|
+
adapters: list = []
|
|
70
|
+
adapters.append(CursorAdapter(scope="project", base_dir=base,
|
|
71
|
+
sync_log_db=db, recall_fn=recall))
|
|
72
|
+
adapters.append(CursorAdapter(scope="global", base_dir=Path.home(),
|
|
73
|
+
sync_log_db=db, recall_fn=recall))
|
|
74
|
+
adapters.append(AntigravityAdapter(scope="workspace", base_dir=base,
|
|
75
|
+
sync_log_db=db, recall_fn=recall))
|
|
76
|
+
adapters.append(AntigravityAdapter(scope="global", base_dir=Path.home(),
|
|
77
|
+
sync_log_db=db, recall_fn=recall))
|
|
78
|
+
adapters.append(CopilotAdapter(base_dir=base, sync_log_db=db,
|
|
79
|
+
recall_fn=recall))
|
|
80
|
+
return adapters
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# `slm connect` (LLD-05 mode)
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cmd_connect_cross_platform(args: Namespace) -> None:
|
|
89
|
+
"""LLD-05 mode: orchestrate cross-platform adapters."""
|
|
90
|
+
adapters = build_default_adapters()
|
|
91
|
+
connector = CrossPlatformConnector(adapters)
|
|
92
|
+
|
|
93
|
+
disable_target = getattr(args, "disable", None)
|
|
94
|
+
if disable_target:
|
|
95
|
+
ok = connector.disable(disable_target)
|
|
96
|
+
msg = {"adapter": disable_target, "disabled": ok}
|
|
97
|
+
if getattr(args, "json", False):
|
|
98
|
+
print(_json.dumps(msg))
|
|
99
|
+
else:
|
|
100
|
+
print(f"{'Disabled' if ok else 'Not found'}: {disable_target}")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if getattr(args, "dry_run", False):
|
|
104
|
+
statuses = connector.detect()
|
|
105
|
+
out = [
|
|
106
|
+
{"name": s.name, "active": s.active, "target": s.target_path}
|
|
107
|
+
for s in statuses
|
|
108
|
+
]
|
|
109
|
+
if getattr(args, "json", False):
|
|
110
|
+
print(_json.dumps({"detected": out}))
|
|
111
|
+
else:
|
|
112
|
+
for row in out:
|
|
113
|
+
mark = "[+]" if row["active"] else "[-]"
|
|
114
|
+
print(f" {mark} {row['name']:25s} {row['target']}")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
results = connector.connect()
|
|
118
|
+
if getattr(args, "json", False):
|
|
119
|
+
print(_json.dumps({"results": results}))
|
|
120
|
+
else:
|
|
121
|
+
for name, res in results.items():
|
|
122
|
+
print(f" {name}: {res}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# `slm context prestage`
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _render_markdown(payload) -> str:
|
|
131
|
+
lines = [
|
|
132
|
+
"# --- SLM Context ---",
|
|
133
|
+
f"_Version {payload.version} — {payload.generated_at}_",
|
|
134
|
+
"",
|
|
135
|
+
"## Topics",
|
|
136
|
+
format_topics(payload),
|
|
137
|
+
"",
|
|
138
|
+
"## Entities",
|
|
139
|
+
format_entities(payload),
|
|
140
|
+
"",
|
|
141
|
+
"## Recent decisions",
|
|
142
|
+
format_decisions(payload),
|
|
143
|
+
"",
|
|
144
|
+
"## Project memories",
|
|
145
|
+
format_memories(payload),
|
|
146
|
+
]
|
|
147
|
+
return "\n".join(lines)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def cmd_context(args: Namespace) -> None:
|
|
151
|
+
"""Dispatcher for ``slm context <subcommand>``."""
|
|
152
|
+
sub = getattr(args, "subcommand", None) or "prestage"
|
|
153
|
+
if sub != "prestage":
|
|
154
|
+
print(f"Unknown subcommand: {sub}")
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
query = getattr(args, "query", "") or ""
|
|
158
|
+
limit = int(getattr(args, "limit", 5))
|
|
159
|
+
profile_id = getattr(args, "profile_id", "default")
|
|
160
|
+
|
|
161
|
+
if getattr(args, "tool", False):
|
|
162
|
+
# Direct MCP-like tool response.
|
|
163
|
+
result = prestage_context(
|
|
164
|
+
query, limit=limit, profile_id=profile_id,
|
|
165
|
+
recall_fn=_get_recall_fn(),
|
|
166
|
+
)
|
|
167
|
+
print(_json.dumps(result, indent=2))
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# Build a full markdown / JSON payload for CLI consumption.
|
|
171
|
+
payload = build_payload(
|
|
172
|
+
profile_id, "project", Path.cwd(),
|
|
173
|
+
recall_fn=_get_recall_fn(),
|
|
174
|
+
)
|
|
175
|
+
if getattr(args, "json", False):
|
|
176
|
+
print(_json.dumps({
|
|
177
|
+
"query": query,
|
|
178
|
+
"topics": list(payload.topics),
|
|
179
|
+
"entities": list(payload.entities),
|
|
180
|
+
"decisions": list(payload.recent_decisions),
|
|
181
|
+
"memories": list(payload.project_memories),
|
|
182
|
+
"generated_at": payload.generated_at,
|
|
183
|
+
}))
|
|
184
|
+
return
|
|
185
|
+
print(_render_markdown(payload))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = (
|
|
189
|
+
"build_default_adapters",
|
|
190
|
+
"cmd_context",
|
|
191
|
+
"cmd_connect_cross_platform",
|
|
192
|
+
)
|
|
@@ -473,8 +473,22 @@ class DaemonHandler(BaseHTTPRequestHandler):
|
|
|
473
473
|
query = params.get("q", [""])[0]
|
|
474
474
|
limit = int(params.get("limit", ["20"])[0])
|
|
475
475
|
|
|
476
|
+
# S9-DASH-02: session_id for outcome-queue enqueue.
|
|
477
|
+
# Priority: ?session_id= query arg > X-SLM-Session-Id
|
|
478
|
+
# header > synthetic "cli:<ts>". Without any of these
|
|
479
|
+
# the recall still works — it just doesn't produce a
|
|
480
|
+
# pending_outcome (hook-based signals can't match).
|
|
481
|
+
session_id = params.get("session_id", [""])[0]
|
|
482
|
+
if not session_id:
|
|
483
|
+
session_id = self.headers.get("X-SLM-Session-Id", "")
|
|
484
|
+
if not session_id:
|
|
485
|
+
import time as _t
|
|
486
|
+
session_id = f"http:{int(_t.time() * 1000)}"
|
|
487
|
+
|
|
476
488
|
engine = _get_engine()
|
|
477
|
-
response = engine.recall(
|
|
489
|
+
response = engine.recall(
|
|
490
|
+
query, limit=limit, session_id=session_id,
|
|
491
|
+
)
|
|
478
492
|
results = [
|
|
479
493
|
{"content": r.fact.content, "score": round(r.score, 4),
|
|
480
494
|
"fact_type": getattr(r.fact.fact_type, 'value', str(r.fact.fact_type)),
|