superlocalmemory 3.6.13 → 3.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +17 -0
- package/README.md +187 -741
- package/package.json +12 -5
- package/plugin/.claude-plugin/plugin.json +20 -0
- package/plugin/.mcp.json +12 -0
- package/plugin/CLAUDE.md +43 -0
- package/plugin/_GENERATED.md +6 -0
- package/plugin/agents/slm-memory-advisor.md +43 -0
- package/plugin/agents/slm-optimize-advisor.md +38 -0
- package/plugin/hooks/hooks.json +14 -0
- package/plugin/requirements.txt +1 -0
- package/plugin/scripts/ensure-venv.bat +122 -0
- package/plugin/scripts/ensure-venv.sh +105 -0
- package/plugin/scripts/slm-launch +15 -0
- package/plugin/scripts/slm-launch.bat +17 -0
- package/plugin/settings.json +16 -0
- package/plugin/skills/slm-cache/SKILL.md +140 -0
- package/plugin/skills/slm-compress/SKILL.md +143 -0
- package/plugin/skills/slm-graph/SKILL.md +300 -0
- package/plugin/skills/slm-recall/SKILL.md +196 -0
- package/plugin/skills/slm-remember/SKILL.md +182 -0
- package/plugin/skills/slm-session/SKILL.md +207 -0
- package/plugin/skills/slm-status/SKILL.md +149 -0
- package/plugin-src/.mcp.json +12 -0
- package/plugin-src/agents/slm-memory-advisor.md +43 -0
- package/plugin-src/agents/slm-optimize-advisor.md +38 -0
- package/plugin-src/commands/slm-optimize.md +22 -0
- package/plugin-src/commands/slm-recall.md +16 -0
- package/plugin-src/commands/slm-remember.md +16 -0
- package/plugin-src/commands/slm-status.md +15 -0
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +14 -0
- package/plugin-src/manifest.json +25 -0
- package/plugin-src/requirements.txt +1 -0
- package/plugin-src/rules/AGENTS.md +90 -0
- package/plugin-src/rules/CLAUDE.md.fragment +43 -0
- package/plugin-src/scripts/ensure-venv.bat +122 -0
- package/plugin-src/scripts/ensure-venv.sh +105 -0
- package/plugin-src/scripts/slm-launch +15 -0
- package/plugin-src/scripts/slm-launch.bat +17 -0
- package/plugin-src/settings.json +16 -0
- package/plugin-src/skills/slm-cache/SKILL.md +140 -0
- package/plugin-src/skills/slm-compress/SKILL.md +143 -0
- package/plugin-src/skills/slm-graph/SKILL.md +300 -0
- package/plugin-src/skills/slm-recall/SKILL.md +196 -0
- package/plugin-src/skills/slm-remember/SKILL.md +182 -0
- package/plugin-src/skills/slm-session/SKILL.md +207 -0
- package/plugin-src/skills/slm-status/SKILL.md +149 -0
- package/pyproject.toml +6 -2
- package/scripts/__tests__/build-plugin.test.mjs +613 -0
- package/scripts/_savings_math.py +270 -0
- package/scripts/build-plugin.js +742 -0
- package/scripts/dogfood_savings.py +490 -0
- package/scripts/install-skills.ps1 +4 -334
- package/scripts/install-skills.sh +4 -435
- package/scripts/postinstall-interactive.js +0 -27
- package/scripts/postinstall.js +21 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +115 -0
- package/src/superlocalmemory/cli/commands.py +348 -39
- package/src/superlocalmemory/cli/main.py +47 -4
- package/src/superlocalmemory/cli/setup_wizard.py +20 -6
- package/src/superlocalmemory/core/config.py +79 -9
- package/src/superlocalmemory/core/embeddings.py +10 -5
- package/src/superlocalmemory/core/engine.py +2 -2
- package/src/superlocalmemory/hooks/claude_code_hooks.py +27 -3
- package/src/superlocalmemory/hooks/portable_kit.py +506 -0
- package/src/superlocalmemory/infra/cloud_backup.py +99 -23
- package/src/superlocalmemory/mcp/cli_fallback.py +602 -0
- package/src/superlocalmemory/mcp/server.py +75 -4
- package/src/superlocalmemory/mcp/tools_code_graph.py +3 -3
- package/src/superlocalmemory/mcp/tools_core.py +12 -4
- package/src/superlocalmemory/optimize/cache/boundary_store.py +25 -6
- package/src/superlocalmemory/optimize/cache/centroid_store.py +27 -4
- package/src/superlocalmemory/optimize/cache/manager.py +92 -6
- package/src/superlocalmemory/optimize/cache/semantic.py +20 -1
- package/src/superlocalmemory/optimize/compress/ccr.py +12 -0
- package/src/superlocalmemory/optimize/compress/router.py +46 -13
- package/src/superlocalmemory/optimize/config/schema.py +6 -0
- package/src/superlocalmemory/optimize/proxy/_helpers.py +111 -8
- package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +14 -4
- package/src/superlocalmemory/optimize/proxy/gemini_surface.py +23 -6
- package/src/superlocalmemory/optimize/proxy/openai_surface.py +10 -4
- package/src/superlocalmemory/optimize/proxy/server.py +11 -0
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +246 -0
- package/src/superlocalmemory/optimize/storage/db.py +30 -0
- package/src/superlocalmemory/server/recall_serializer.py +3 -1
- package/src/superlocalmemory/server/unified_daemon.py +24 -6
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +18 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +5 -0
- package/src/superlocalmemory/ui/index.html +2 -2
- package/src/superlocalmemory/ui/js/core.js +98 -0
- package/src/superlocalmemory/ui/js/dashboard.js +8 -1
- package/src/superlocalmemory/ui/js/ide-status.js +16 -3
- package/src/superlocalmemory/ui/js/math-health.js +15 -3
- package/src/superlocalmemory/ui/js/optimize.js +18 -2
- package/src/superlocalmemory/ui/js/trust-dashboard.js +10 -1
- package/src/superlocalmemory.egg-info/PKG-INFO +189 -742
- package/src/superlocalmemory.egg-info/SOURCES.txt +6 -9
- package/src/superlocalmemory.egg-info/requires.txt +1 -0
- package/ide/skills/slm-build-graph/SKILL.md +0 -423
- package/ide/skills/slm-list-recent/SKILL.md +0 -348
- package/ide/skills/slm-recall/SKILL.md +0 -326
- package/ide/skills/slm-remember/SKILL.md +0 -194
- package/ide/skills/slm-show-patterns/SKILL.md +0 -224
- package/ide/skills/slm-status/SKILL.md +0 -363
- package/ide/skills/slm-switch-profile/SKILL.md +0 -442
- package/skills/slm-build-graph/SKILL.md +0 -423
- package/skills/slm-list-recent/SKILL.md +0 -348
- package/skills/slm-optimize/README.md +0 -55
- package/skills/slm-optimize/SKILL.md +0 -139
- package/skills/slm-recall/SKILL.md +0 -343
- package/skills/slm-remember/SKILL.md +0 -194
- package/skills/slm-show-patterns/SKILL.md +0 -224
- package/skills/slm-status/SKILL.md +0 -363
- package/skills/slm-switch-profile/SKILL.md +0 -442
- package/src/superlocalmemory/cli/doctor_cmd.py +0 -152
- package/src/superlocalmemory/skills/slm-build-graph/SKILL.md +0 -423
- package/src/superlocalmemory/skills/slm-list-recent/SKILL.md +0 -348
- package/src/superlocalmemory/skills/slm-recall/SKILL.md +0 -343
- package/src/superlocalmemory/skills/slm-remember/SKILL.md +0 -194
- package/src/superlocalmemory/skills/slm-show-patterns/SKILL.md +0 -224
- package/src/superlocalmemory/skills/slm-status/SKILL.md +0 -363
- package/src/superlocalmemory/skills/slm-switch-profile/SKILL.md +0 -442
|
@@ -780,8 +780,97 @@ def _cmd_context_dispatch(args: Namespace) -> None:
|
|
|
780
780
|
cmd_context(args)
|
|
781
781
|
|
|
782
782
|
|
|
783
|
+
def _agents_md_source_factory():
|
|
784
|
+
"""Return a callable that reads the WP-05 AGENTS.md content, or None on failure.
|
|
785
|
+
|
|
786
|
+
Source: plugin-src/rules/AGENTS.md (relative to package root).
|
|
787
|
+
Gracefully skips if absent — never fails the MCP write.
|
|
788
|
+
"""
|
|
789
|
+
from pathlib import Path
|
|
790
|
+
|
|
791
|
+
# Resolve relative to the package root (src/superlocalmemory/../../)
|
|
792
|
+
_pkg_root = Path(__file__).resolve().parents[3]
|
|
793
|
+
_agents_src = _pkg_root / "plugin-src" / "rules" / "AGENTS.md"
|
|
794
|
+
|
|
795
|
+
def _read() -> str | None:
|
|
796
|
+
if _agents_src.exists():
|
|
797
|
+
return _agents_src.read_text(encoding="utf-8")
|
|
798
|
+
logger.warning(
|
|
799
|
+
"WP-05 AGENTS.md not found at %s — skipping AGENTS.md write", _agents_src
|
|
800
|
+
)
|
|
801
|
+
return None
|
|
802
|
+
|
|
803
|
+
return _read
|
|
804
|
+
|
|
805
|
+
|
|
783
806
|
def cmd_connect(args: Namespace) -> None:
|
|
784
|
-
"""Configure IDE integrations.
|
|
807
|
+
"""Configure IDE integrations.
|
|
808
|
+
|
|
809
|
+
Dispatch priority (WP-08):
|
|
810
|
+
1. ``slm connect <ide>`` where ide ∈ IDE_MATRIX → portable_kit.connect_ide
|
|
811
|
+
(MCP-wiring + AGENTS.md; includes claude-code short-circuit to WP-06).
|
|
812
|
+
2. ``--cross-platform`` / ``--disable`` → LLD-05 CrossPlatformConnector.
|
|
813
|
+
3. Bare ``slm connect`` / ``--list`` → legacy IDEConnector (markdown-rules).
|
|
814
|
+
"""
|
|
815
|
+
ide_arg = getattr(args, "ide", None)
|
|
816
|
+
|
|
817
|
+
# WP-08: intercept known IDE_MATRIX ids before legacy branches (CRIT-1)
|
|
818
|
+
if ide_arg is not None:
|
|
819
|
+
from superlocalmemory.hooks.portable_kit import (
|
|
820
|
+
IDE_MATRIX,
|
|
821
|
+
connect_ide,
|
|
822
|
+
supported_ides,
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
if ide_arg in IDE_MATRIX:
|
|
826
|
+
here = getattr(args, "here", False)
|
|
827
|
+
profile = getattr(args, "profile", None)
|
|
828
|
+
project = None
|
|
829
|
+
if here:
|
|
830
|
+
import pathlib
|
|
831
|
+
project = pathlib.Path.cwd()
|
|
832
|
+
|
|
833
|
+
result = connect_ide(
|
|
834
|
+
ide_arg,
|
|
835
|
+
home=None,
|
|
836
|
+
project=project,
|
|
837
|
+
here=here,
|
|
838
|
+
profile=profile,
|
|
839
|
+
agents_md_source=_agents_md_source_factory(),
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
if getattr(args, "json", False):
|
|
843
|
+
from superlocalmemory.cli.json_output import json_print
|
|
844
|
+
json_print("connect", data=result)
|
|
845
|
+
return
|
|
846
|
+
|
|
847
|
+
if result["error"]:
|
|
848
|
+
print(f"Error: {result['error']}", file=sys.stderr)
|
|
849
|
+
print(
|
|
850
|
+
f"Supported IDEs: {', '.join(supported_ides())}",
|
|
851
|
+
file=sys.stderr,
|
|
852
|
+
)
|
|
853
|
+
sys.exit(1)
|
|
854
|
+
|
|
855
|
+
status_sym = {"wrote": "[+]", "merged": "[~]", "unchanged": "[=]",
|
|
856
|
+
"skipped": "[s]", "error": "[!]"}.get(
|
|
857
|
+
result["mcp_config"], "[?]"
|
|
858
|
+
)
|
|
859
|
+
print(
|
|
860
|
+
f"{status_sym} {ide_arg}: mcp_config={result['mcp_config']} "
|
|
861
|
+
f"path={result['mcp_path']}"
|
|
862
|
+
)
|
|
863
|
+
print(f" agents_md={result['agents_md']}")
|
|
864
|
+
return
|
|
865
|
+
|
|
866
|
+
# Unknown ide — list supported and exit non-zero
|
|
867
|
+
from superlocalmemory.hooks.portable_kit import supported_ides
|
|
868
|
+
print(
|
|
869
|
+
f"Unknown IDE '{ide_arg}'.\nSupported: {', '.join(supported_ides())}",
|
|
870
|
+
file=sys.stderr,
|
|
871
|
+
)
|
|
872
|
+
sys.exit(1)
|
|
873
|
+
|
|
785
874
|
# Route --disable <name> and --cross-platform to the LLD-05 orchestrator.
|
|
786
875
|
if getattr(args, "disable", None) or getattr(args, "cross_platform", False):
|
|
787
876
|
from superlocalmemory.cli.context_commands import (
|
|
@@ -1318,14 +1407,60 @@ def cmd_status(args: Namespace) -> None:
|
|
|
1318
1407
|
|
|
1319
1408
|
if getattr(args, 'json', False):
|
|
1320
1409
|
from superlocalmemory.cli.json_output import json_print
|
|
1410
|
+
|
|
1411
|
+
# WP-02 D8: canonical key set — db_size_mb always present (0.0 if absent).
|
|
1412
|
+
db_size_mb = 0.0
|
|
1413
|
+
if config.db_path.exists():
|
|
1414
|
+
db_size_mb = round(config.db_path.stat().st_size / 1024 / 1024, 2)
|
|
1415
|
+
|
|
1416
|
+
# Open engine for counts (json branch only — LLD Decision B).
|
|
1417
|
+
# Fail-open to 0 on any error; status must never crash.
|
|
1418
|
+
# Guard on db existence: `slm status --json` must stay observational —
|
|
1419
|
+
# opening the engine on a fresh install would create + migrate the db
|
|
1420
|
+
# (MemoryEngine.initialize → DatabaseManager mkdir/connect/DDL). A
|
|
1421
|
+
# previously read-only command must not acquire a write side-effect.
|
|
1422
|
+
fact_count = 0
|
|
1423
|
+
entity_count = 0
|
|
1424
|
+
edge_count = 0
|
|
1425
|
+
eng = None
|
|
1426
|
+
if config.db_path.exists():
|
|
1427
|
+
try:
|
|
1428
|
+
from superlocalmemory.core.engine import MemoryEngine
|
|
1429
|
+
from superlocalmemory.core.engine_capabilities import Capabilities
|
|
1430
|
+
eng = MemoryEngine(config, capabilities=Capabilities.LIGHT)
|
|
1431
|
+
eng.initialize()
|
|
1432
|
+
pid = config.active_profile
|
|
1433
|
+
fact_count = eng._db.get_fact_count(pid)
|
|
1434
|
+
rows = eng._db.execute(
|
|
1435
|
+
"SELECT COUNT(*) AS c FROM canonical_entities WHERE profile_id = ?",
|
|
1436
|
+
(pid,),
|
|
1437
|
+
)
|
|
1438
|
+
entity_count = int(dict(rows[0])["c"]) if rows else 0
|
|
1439
|
+
rows2 = eng._db.execute(
|
|
1440
|
+
"SELECT COUNT(*) AS c FROM graph_edges WHERE profile_id = ?",
|
|
1441
|
+
(pid,),
|
|
1442
|
+
)
|
|
1443
|
+
edge_count = int(dict(rows2[0])["c"]) if rows2 else 0
|
|
1444
|
+
except Exception:
|
|
1445
|
+
logger.debug("cmd_status: engine count query failed; using 0s", exc_info=True)
|
|
1446
|
+
finally:
|
|
1447
|
+
if eng is not None:
|
|
1448
|
+
try:
|
|
1449
|
+
eng.close()
|
|
1450
|
+
except Exception:
|
|
1451
|
+
pass
|
|
1452
|
+
|
|
1321
1453
|
data = {
|
|
1322
1454
|
"mode": config.mode.value.upper(),
|
|
1323
1455
|
"provider": config.llm.provider or "none",
|
|
1456
|
+
"profile": config.active_profile,
|
|
1324
1457
|
"base_dir": str(config.base_dir),
|
|
1325
1458
|
"db_path": str(config.db_path),
|
|
1459
|
+
"db_size_mb": db_size_mb,
|
|
1460
|
+
"fact_count": fact_count,
|
|
1461
|
+
"entity_count": entity_count,
|
|
1462
|
+
"edge_count": edge_count,
|
|
1326
1463
|
}
|
|
1327
|
-
if config.db_path.exists():
|
|
1328
|
-
data["db_size_mb"] = round(config.db_path.stat().st_size / 1024 / 1024, 2)
|
|
1329
1464
|
json_print("status", data=data, next_actions=[
|
|
1330
1465
|
{"command": "slm health --json", "description": "Check math layer health"},
|
|
1331
1466
|
{"command": "slm list --json", "description": "List recent memories"},
|
|
@@ -1406,6 +1541,63 @@ def cmd_health(args: Namespace) -> None:
|
|
|
1406
1541
|
print(f" Mode: {config.mode.value.upper()}")
|
|
1407
1542
|
|
|
1408
1543
|
|
|
1544
|
+
def _gather_optimize_surface_b() -> dict:
|
|
1545
|
+
"""Gather Surface-B health data for slm doctor.
|
|
1546
|
+
|
|
1547
|
+
Pure data-gather — never raises, never prints, never starts the
|
|
1548
|
+
ConfigStore watchdog thread. Reads daemon-persisted metrics only
|
|
1549
|
+
(CacheDB.metrics_load), never the in-process KV counters.
|
|
1550
|
+
|
|
1551
|
+
Returns a dict with keys:
|
|
1552
|
+
enabled, cache_enabled, compress_enabled, proxy_enabled,
|
|
1553
|
+
compress_runs, tokens_saved, cache_hits, cache_misses,
|
|
1554
|
+
db_present, error
|
|
1555
|
+
"""
|
|
1556
|
+
from pathlib import Path
|
|
1557
|
+
from superlocalmemory.optimize.storage.db import CacheDB
|
|
1558
|
+
|
|
1559
|
+
result: dict = {
|
|
1560
|
+
"enabled": False,
|
|
1561
|
+
"cache_enabled": False,
|
|
1562
|
+
"compress_enabled": False,
|
|
1563
|
+
"proxy_enabled": False,
|
|
1564
|
+
"compress_runs": 0,
|
|
1565
|
+
"tokens_saved": 0,
|
|
1566
|
+
"cache_hits": 0,
|
|
1567
|
+
"cache_misses": 0,
|
|
1568
|
+
"db_present": False,
|
|
1569
|
+
"error": "",
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
# Step 1: read optimize config — NO watchdog start.
|
|
1573
|
+
try:
|
|
1574
|
+
from superlocalmemory.optimize.config.store import ConfigStore
|
|
1575
|
+
cfg = ConfigStore().get()
|
|
1576
|
+
result["enabled"] = cfg.enabled
|
|
1577
|
+
result["cache_enabled"] = cfg.cache_enabled
|
|
1578
|
+
result["compress_enabled"] = cfg.compress_enabled
|
|
1579
|
+
result["proxy_enabled"] = cfg.proxy_enabled
|
|
1580
|
+
except Exception as exc: # noqa: BLE001
|
|
1581
|
+
result["error"] = str(exc)
|
|
1582
|
+
# enabled stays False — safe default
|
|
1583
|
+
|
|
1584
|
+
# Step 2: read persisted metrics from llmcache.db (daemon-flushed, ≤60s stale).
|
|
1585
|
+
try:
|
|
1586
|
+
db_path = Path.home() / ".superlocalmemory" / "llmcache.db"
|
|
1587
|
+
result["db_present"] = db_path.exists()
|
|
1588
|
+
if result["db_present"]:
|
|
1589
|
+
snap = CacheDB.get_default().metrics_load()
|
|
1590
|
+
result["compress_runs"] = snap.compress_runs
|
|
1591
|
+
result["tokens_saved"] = snap.tokens_saved_compress
|
|
1592
|
+
result["cache_hits"] = snap.hits
|
|
1593
|
+
result["cache_misses"] = snap.misses
|
|
1594
|
+
except Exception as exc: # noqa: BLE001
|
|
1595
|
+
prior = result["error"]
|
|
1596
|
+
result["error"] = (prior + "; " if prior else "") + "metrics read failed"
|
|
1597
|
+
|
|
1598
|
+
return result
|
|
1599
|
+
|
|
1600
|
+
|
|
1409
1601
|
def cmd_doctor(args: Namespace) -> None:
|
|
1410
1602
|
"""Comprehensive pre-flight check — verify everything works.
|
|
1411
1603
|
|
|
@@ -1680,6 +1872,89 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
1680
1872
|
else:
|
|
1681
1873
|
_check("Database", "PASS", "not yet created (will initialize on first use)")
|
|
1682
1874
|
|
|
1875
|
+
# 11. PEP 668 advisory — WP-07: detect EXTERNALLY-MANAGED marker and
|
|
1876
|
+
# recommend pipx when the system Python is managed by the OS package
|
|
1877
|
+
# manager (e.g. Homebrew, Debian/Ubuntu, Fedora 38+).
|
|
1878
|
+
try:
|
|
1879
|
+
import sysconfig as _sc
|
|
1880
|
+
_stdlib = _sc.get_path("stdlib")
|
|
1881
|
+
if _stdlib:
|
|
1882
|
+
_em_marker = Path(_stdlib) / "EXTERNALLY-MANAGED"
|
|
1883
|
+
if _em_marker.exists():
|
|
1884
|
+
_check(
|
|
1885
|
+
"PEP 668 / Install method",
|
|
1886
|
+
"WARN",
|
|
1887
|
+
"System Python is externally managed (EXTERNALLY-MANAGED marker found). "
|
|
1888
|
+
"pip install may fail with PEP 668 error.",
|
|
1889
|
+
"Use pipx for an isolated install: pipx install superlocalmemory "
|
|
1890
|
+
"(last-resort only: pip install --break-system-packages superlocalmemory)",
|
|
1891
|
+
)
|
|
1892
|
+
else:
|
|
1893
|
+
_check(
|
|
1894
|
+
"PEP 668 / Install method",
|
|
1895
|
+
"PASS",
|
|
1896
|
+
"No EXTERNALLY-MANAGED marker — standard pip install supported",
|
|
1897
|
+
)
|
|
1898
|
+
except Exception:
|
|
1899
|
+
pass # advisory only — never fail doctor on this check
|
|
1900
|
+
|
|
1901
|
+
# 12. Optimize (Surface B) — reads daemon-persisted metrics (≤60s stale).
|
|
1902
|
+
info = _gather_optimize_surface_b()
|
|
1903
|
+
_enabled = info["enabled"]
|
|
1904
|
+
_error = info.get("error", "")
|
|
1905
|
+
if not _enabled:
|
|
1906
|
+
_check(
|
|
1907
|
+
"Optimize (Surface B)",
|
|
1908
|
+
"WARN",
|
|
1909
|
+
"disabled (optimize.json enabled=false) — caching/compression not active"
|
|
1910
|
+
+ (f" [{_error}]" if _error else ""),
|
|
1911
|
+
fix="Enable via dashboard Optimize tab or set enabled=true"
|
|
1912
|
+
" in ~/.superlocalmemory/optimize.json",
|
|
1913
|
+
)
|
|
1914
|
+
else:
|
|
1915
|
+
_surfaces = []
|
|
1916
|
+
if info["cache_enabled"]:
|
|
1917
|
+
_surfaces.append("cache")
|
|
1918
|
+
if info["compress_enabled"]:
|
|
1919
|
+
_surfaces.append("compress")
|
|
1920
|
+
if info["proxy_enabled"]:
|
|
1921
|
+
_surfaces.append("proxy")
|
|
1922
|
+
_stats = (
|
|
1923
|
+
f"compress_runs={info['compress_runs']}"
|
|
1924
|
+
f" tokens_saved={info['tokens_saved']}"
|
|
1925
|
+
f" cache_hits={info['cache_hits']}"
|
|
1926
|
+
f" cache_misses={info['cache_misses']}"
|
|
1927
|
+
)
|
|
1928
|
+
_surface_str = ",".join(_surfaces) if _surfaces else "(none)"
|
|
1929
|
+
if not _surfaces:
|
|
1930
|
+
_check(
|
|
1931
|
+
"Optimize (Surface B)",
|
|
1932
|
+
"WARN",
|
|
1933
|
+
f"enabled [{_surface_str}] but no surface active"
|
|
1934
|
+
+ (f" [{_error}]" if _error else ""),
|
|
1935
|
+
)
|
|
1936
|
+
elif not info["db_present"]:
|
|
1937
|
+
_check(
|
|
1938
|
+
"Optimize (Surface B)",
|
|
1939
|
+
"WARN",
|
|
1940
|
+
f"enabled [{_surface_str}] {_stats}"
|
|
1941
|
+
" but no metrics yet (llmcache.db not created)"
|
|
1942
|
+
+ (f" [{_error}]" if _error else ""),
|
|
1943
|
+
fix="slm serve start",
|
|
1944
|
+
)
|
|
1945
|
+
elif _error:
|
|
1946
|
+
_check(
|
|
1947
|
+
"Optimize (Surface B)",
|
|
1948
|
+
"WARN",
|
|
1949
|
+
f"enabled [{_surface_str}] {_stats} (partial: {_error})",
|
|
1950
|
+
)
|
|
1951
|
+
else:
|
|
1952
|
+
_check(
|
|
1953
|
+
"Optimize (Surface B)",
|
|
1954
|
+
"PASS",
|
|
1955
|
+
f"enabled [{_surface_str}] {_stats}",
|
|
1956
|
+
)
|
|
1957
|
+
|
|
1683
1958
|
# Summary
|
|
1684
1959
|
if use_json:
|
|
1685
1960
|
from superlocalmemory.cli.json_output import json_print
|
|
@@ -2003,14 +2278,82 @@ def cmd_profile(args: Namespace) -> None:
|
|
|
2003
2278
|
# -- Active Memory commands (V3.1) ------------------------------------------
|
|
2004
2279
|
|
|
2005
2280
|
|
|
2281
|
+
def _cmd_init_auto(
|
|
2282
|
+
args: Namespace,
|
|
2283
|
+
slm_data_dir: "Path",
|
|
2284
|
+
config_exists: bool,
|
|
2285
|
+
force: bool,
|
|
2286
|
+
) -> None:
|
|
2287
|
+
"""WP-07: non-interactive --auto branch for slm init.
|
|
2288
|
+
|
|
2289
|
+
Best-effort at every step; only exits non-zero when config save fails.
|
|
2290
|
+
No TTY required. Does NOT run IDE connect (AC6).
|
|
2291
|
+
"""
|
|
2292
|
+
from pathlib import Path
|
|
2293
|
+
from superlocalmemory.core.config import SLMConfig
|
|
2294
|
+
from superlocalmemory.storage.models import Mode
|
|
2295
|
+
from superlocalmemory.cli.setup_wizard import _mark_complete
|
|
2296
|
+
|
|
2297
|
+
# Step 1: write mode-A config (create-if-absent or --force).
|
|
2298
|
+
# Pass slm_data_dir explicitly so env-overridden paths are respected even
|
|
2299
|
+
# when DEFAULT_BASE_DIR was evaluated before the env var was set (e.g. tests).
|
|
2300
|
+
if force or not config_exists:
|
|
2301
|
+
try:
|
|
2302
|
+
cfg = SLMConfig.for_mode(Mode.A, base_dir=slm_data_dir)
|
|
2303
|
+
cfg.save(mode_change=True)
|
|
2304
|
+
except Exception as exc:
|
|
2305
|
+
print(f"[ERROR] slm init --auto: config save failed: {exc}", file=sys.stderr)
|
|
2306
|
+
sys.exit(1)
|
|
2307
|
+
|
|
2308
|
+
# Step 2: mark complete (write .setup-complete sentinel).
|
|
2309
|
+
# Write the sentinel directly using slm_data_dir to avoid the module-level
|
|
2310
|
+
# _SLM_HOME resolved at import time (tests set the env var after import).
|
|
2311
|
+
try:
|
|
2312
|
+
import platform
|
|
2313
|
+
import time as _time
|
|
2314
|
+
sentinel = slm_data_dir / ".setup-complete"
|
|
2315
|
+
slm_data_dir.mkdir(parents=True, exist_ok=True)
|
|
2316
|
+
sentinel.write_text(
|
|
2317
|
+
f"setup_completed={_time.strftime('%Y-%m-%dT%H:%M:%S')}\n"
|
|
2318
|
+
f"python={sys.executable}\n"
|
|
2319
|
+
f"platform={platform.system()}\n"
|
|
2320
|
+
f"version={platform.python_version()}\n"
|
|
2321
|
+
)
|
|
2322
|
+
except Exception:
|
|
2323
|
+
pass # best-effort — sentinel is advisory only
|
|
2324
|
+
|
|
2325
|
+
# Step 3: install hooks if ~/.claude exists.
|
|
2326
|
+
try:
|
|
2327
|
+
claude_dir = Path.home() / ".claude"
|
|
2328
|
+
if claude_dir.exists():
|
|
2329
|
+
from superlocalmemory.hooks.claude_code_hooks import install_hooks
|
|
2330
|
+
install_hooks(include_gate=getattr(args, "gate", False))
|
|
2331
|
+
except Exception:
|
|
2332
|
+
pass # best-effort
|
|
2333
|
+
|
|
2334
|
+
# Step 4: warmup best-effort (don't block if models not present).
|
|
2335
|
+
# Skipped in --auto to keep startup fast for CI; user can run slm warmup.
|
|
2336
|
+
|
|
2337
|
+
print("[OK] slm init --auto: setup complete (mode A, non-interactive)", file=sys.stderr)
|
|
2338
|
+
|
|
2339
|
+
|
|
2006
2340
|
def cmd_init(args: Namespace) -> None:
|
|
2007
2341
|
"""One-command setup: mode + hooks + IDE connect + warmup."""
|
|
2008
2342
|
from pathlib import Path
|
|
2343
|
+
from superlocalmemory.cli._lazy_init import slm_home
|
|
2009
2344
|
from superlocalmemory.core.config import SLMConfig
|
|
2010
2345
|
|
|
2011
2346
|
force = getattr(args, "force", False)
|
|
2347
|
+
auto = getattr(args, "auto", False)
|
|
2348
|
+
|
|
2349
|
+
slm_data_dir = slm_home()
|
|
2350
|
+
config_exists = (slm_data_dir / "config.json").exists()
|
|
2012
2351
|
|
|
2013
|
-
|
|
2352
|
+
# WP-07: --auto branch — fully non-interactive, no TTY required (AC6).
|
|
2353
|
+
if auto:
|
|
2354
|
+
os.environ["SLM_NON_INTERACTIVE"] = "1"
|
|
2355
|
+
_cmd_init_auto(args, slm_data_dir, config_exists, force)
|
|
2356
|
+
return
|
|
2014
2357
|
|
|
2015
2358
|
print()
|
|
2016
2359
|
print("SuperLocalMemory — One-Time Setup")
|
|
@@ -2114,41 +2457,7 @@ def cmd_hooks(args: Namespace) -> None:
|
|
|
2114
2457
|
if include_gate:
|
|
2115
2458
|
print(" Gate: ON (enforces session_init — experimental)")
|
|
2116
2459
|
print(" SLM: Hooks installed into Claude Code (slm hooks remove to undo)")
|
|
2117
|
-
|
|
2118
|
-
# etc. are available immediately in Claude Code regardless of
|
|
2119
|
-
# whether the user installed via npm or pip.
|
|
2120
|
-
try:
|
|
2121
|
-
import importlib.resources as _ir
|
|
2122
|
-
import importlib.util as _iu
|
|
2123
|
-
import shutil as _sh
|
|
2124
|
-
from pathlib import Path as _P
|
|
2125
|
-
|
|
2126
|
-
claude_skills_dir = _P.home() / ".claude" / "skills"
|
|
2127
|
-
claude_skills_dir.mkdir(parents=True, exist_ok=True)
|
|
2128
|
-
|
|
2129
|
-
# Try Python-package bundled skills first (works for both
|
|
2130
|
-
# pip and npm users who have the Python pkg installed).
|
|
2131
|
-
pkg_skills: _P | None = None
|
|
2132
|
-
spec = _iu.find_spec("superlocalmemory")
|
|
2133
|
-
if spec and spec.submodule_search_locations:
|
|
2134
|
-
candidate = _P(list(spec.submodule_search_locations)[0]) / "skills"
|
|
2135
|
-
if candidate.is_dir():
|
|
2136
|
-
pkg_skills = candidate
|
|
2137
|
-
|
|
2138
|
-
if pkg_skills:
|
|
2139
|
-
installed_skills = 0
|
|
2140
|
-
for d in pkg_skills.iterdir():
|
|
2141
|
-
if d.is_dir():
|
|
2142
|
-
src_skill = d / "SKILL.md"
|
|
2143
|
-
if src_skill.exists():
|
|
2144
|
-
dst = claude_skills_dir / (d.name + ".md")
|
|
2145
|
-
_sh.copy2(str(src_skill), str(dst))
|
|
2146
|
-
installed_skills += 1
|
|
2147
|
-
if installed_skills:
|
|
2148
|
-
print(f" Skills: {installed_skills} skills installed → {claude_skills_dir}")
|
|
2149
|
-
print(" Use /slm-recall, /slm-remember, /slm-status in Claude Code")
|
|
2150
|
-
except Exception as _skill_exc:
|
|
2151
|
-
pass # non-fatal — skills can be installed via install-skills.sh
|
|
2460
|
+
|
|
2152
2461
|
else:
|
|
2153
2462
|
print(f"Installation failed: {result['errors']}")
|
|
2154
2463
|
elif action == "remove":
|
|
@@ -24,6 +24,8 @@ _os.environ.setdefault('TORCH_DEVICE', 'cpu')
|
|
|
24
24
|
import argparse
|
|
25
25
|
import sys
|
|
26
26
|
|
|
27
|
+
from superlocalmemory.core.config import CANONICAL_RECALL_LIMIT
|
|
28
|
+
|
|
27
29
|
_HELP_EPILOG = """\
|
|
28
30
|
operating modes:
|
|
29
31
|
Mode A Local Guardian — Zero cloud, zero LLM. All processing stays on
|
|
@@ -76,6 +78,17 @@ def main() -> None:
|
|
|
76
78
|
handle_hook(sys.argv[2])
|
|
77
79
|
return
|
|
78
80
|
|
|
81
|
+
# WP-07: lazy first-run init — runs after hook/mcp fast-paths so stdout
|
|
82
|
+
# is never polluted on those paths (CRIT-3, MCP JSON-RPC purity).
|
|
83
|
+
# Guarded: any failure must not crash the CLI (AC4).
|
|
84
|
+
_is_mcp_cmd = len(sys.argv) >= 2 and sys.argv[1] == "mcp"
|
|
85
|
+
if not _is_mcp_cmd:
|
|
86
|
+
try:
|
|
87
|
+
from superlocalmemory.cli._lazy_init import _ensure_initialized
|
|
88
|
+
_ensure_initialized()
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
79
92
|
from superlocalmemory.cli.json_output import _get_version
|
|
80
93
|
_ver = _get_version()
|
|
81
94
|
|
|
@@ -101,8 +114,9 @@ def main() -> None:
|
|
|
101
114
|
from superlocalmemory.migrations.v3_4_25_to_v3_4_26 import (
|
|
102
115
|
migrate_if_safe as _migrate_if_safe,
|
|
103
116
|
)
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
# WP-07: route through slm_home() so all 3 env aliases are honoured.
|
|
118
|
+
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
119
|
+
_data = _slm_home()
|
|
106
120
|
_res = _migrate_if_safe(_data)
|
|
107
121
|
if _res.get("status") == "deferred":
|
|
108
122
|
print(
|
|
@@ -140,6 +154,11 @@ def main() -> None:
|
|
|
140
154
|
"--gate", action="store_true",
|
|
141
155
|
help="Enable PreToolUse gate (experimental — blocks tools until session_init)",
|
|
142
156
|
)
|
|
157
|
+
# WP-07: non-interactive auto setup (pip post-install, CI, scripts).
|
|
158
|
+
init_p.add_argument(
|
|
159
|
+
"--auto", action="store_true",
|
|
160
|
+
help="Non-interactive setup: mode A + hooks (no TTY required, for CI/scripts)",
|
|
161
|
+
)
|
|
143
162
|
|
|
144
163
|
setup_p = sub.add_parser("setup", help="Interactive first-time setup wizard")
|
|
145
164
|
setup_p.add_argument(
|
|
@@ -159,11 +178,32 @@ def main() -> None:
|
|
|
159
178
|
)
|
|
160
179
|
|
|
161
180
|
connect_p = sub.add_parser("connect", help="Auto-configure IDE integrations (17+ IDEs)")
|
|
162
|
-
connect_p.add_argument("ide", nargs="?", help="Specific IDE to configure")
|
|
181
|
+
connect_p.add_argument("ide", nargs="?", help="Specific IDE to configure (e.g. cursor, codex, continue)")
|
|
163
182
|
connect_p.add_argument(
|
|
164
183
|
"--list", action="store_true", help="List all supported IDEs",
|
|
165
184
|
)
|
|
166
185
|
connect_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
186
|
+
# WP-08 CRIT-1: declare missing flags so cmd_connect can read them without getattr fallback
|
|
187
|
+
connect_p.add_argument(
|
|
188
|
+
"--here", action="store_true", default=False,
|
|
189
|
+
help="Write config relative to current working directory (project scope)",
|
|
190
|
+
)
|
|
191
|
+
connect_p.add_argument(
|
|
192
|
+
"--cross-platform", action="store_true", dest="cross_platform", default=False,
|
|
193
|
+
help="Use LLD-05 cross-platform adapter orchestrator",
|
|
194
|
+
)
|
|
195
|
+
connect_p.add_argument(
|
|
196
|
+
"--disable", metavar="ADAPTER", default=None,
|
|
197
|
+
help="Disable a specific cross-platform adapter by name",
|
|
198
|
+
)
|
|
199
|
+
connect_p.add_argument(
|
|
200
|
+
"--profile", metavar="PROFILE", default=None,
|
|
201
|
+
help="Inject SLM_MCP_PROFILE env var into the MCP server block (WP-01)",
|
|
202
|
+
)
|
|
203
|
+
connect_p.add_argument(
|
|
204
|
+
"--dry-run", action="store_true", dest="dry_run", default=False,
|
|
205
|
+
help="Show what would be written without making changes",
|
|
206
|
+
)
|
|
167
207
|
|
|
168
208
|
migrate_p = sub.add_parser("migrate", help="Migrate data from V2 to V3 schema")
|
|
169
209
|
migrate_p.add_argument(
|
|
@@ -200,7 +240,10 @@ def main() -> None:
|
|
|
200
240
|
# same search verb the MCP exposes (handlers dict maps both to cmd_recall).
|
|
201
241
|
recall_p = sub.add_parser("recall", aliases=["search"], help="Semantic search with 4-channel retrieval")
|
|
202
242
|
recall_p.add_argument("query", help="Search query")
|
|
203
|
-
recall_p.add_argument(
|
|
243
|
+
recall_p.add_argument(
|
|
244
|
+
"--limit", type=int, default=CANONICAL_RECALL_LIMIT,
|
|
245
|
+
help=f"Max results (default {CANONICAL_RECALL_LIMIT})",
|
|
246
|
+
)
|
|
204
247
|
recall_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
205
248
|
recall_p.add_argument(
|
|
206
249
|
"--fast", action="store_true",
|
|
@@ -28,7 +28,17 @@ from pathlib import Path
|
|
|
28
28
|
# Constants
|
|
29
29
|
# ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
# WP-07: resolve via slm_home() so all 3 env aliases are honoured.
|
|
32
|
+
# Fallback keeps stdlib-only path if the import fails during early bootstrap.
|
|
33
|
+
def _resolve_slm_home() -> Path:
|
|
34
|
+
try:
|
|
35
|
+
from superlocalmemory.cli._lazy_init import slm_home
|
|
36
|
+
return slm_home()
|
|
37
|
+
except Exception:
|
|
38
|
+
return Path(os.environ.get("SL_MEMORY_PATH", "") or Path.home() / ".superlocalmemory")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_SLM_HOME = _resolve_slm_home()
|
|
32
42
|
_SETUP_MARKER = _SLM_HOME / ".setup-complete"
|
|
33
43
|
_EMBED_MODEL = "nomic-ai/nomic-embed-text-v1.5"
|
|
34
44
|
_RERANKER_MODEL = "cross-encoder/ms-marco-MiniLM-L-12-v2"
|
|
@@ -703,14 +713,18 @@ def check_first_use(command: str) -> None:
|
|
|
703
713
|
if is_setup_complete():
|
|
704
714
|
return
|
|
705
715
|
|
|
706
|
-
# Non-interactive: use defaults silently, don't block the command
|
|
716
|
+
# Non-interactive: use defaults silently, don't block the command.
|
|
717
|
+
# CRIT-1: only save config when it does NOT already exist — lazy-init may
|
|
718
|
+
# have already written a valid config.json; overwriting it here would clobber
|
|
719
|
+
# any lazy-init content (e.g. a pre-existing mode-A skeleton).
|
|
707
720
|
if not is_interactive():
|
|
708
|
-
# Just create config with defaults and mark complete
|
|
709
721
|
try:
|
|
710
|
-
from superlocalmemory.core.config import SLMConfig
|
|
722
|
+
from superlocalmemory.core.config import SLMConfig, DEFAULT_BASE_DIR
|
|
711
723
|
from superlocalmemory.storage.models import Mode
|
|
712
|
-
|
|
713
|
-
|
|
724
|
+
config_path = DEFAULT_BASE_DIR / "config.json"
|
|
725
|
+
if not config_path.exists():
|
|
726
|
+
cfg = SLMConfig.for_mode(Mode.A)
|
|
727
|
+
cfg.save(mode_change=True)
|
|
714
728
|
_mark_complete()
|
|
715
729
|
except Exception:
|
|
716
730
|
pass
|