superlocalmemory 3.1.1 → 3.2.0
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 +16 -0
- package/README.md +8 -2
- package/package.json +1 -1
- package/pyproject.toml +9 -7
- package/scripts/postinstall.js +35 -1
- package/src/superlocalmemory/cli/commands.py +308 -19
- package/src/superlocalmemory/cli/main.py +4 -0
- package/src/superlocalmemory/core/config.py +1 -1
- package/src/superlocalmemory/core/embeddings.py +17 -3
- package/src/superlocalmemory/core/engine.py +2 -2
- package/src/superlocalmemory/core/worker_pool.py +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,22 @@ SuperLocalMemory V3 - Intelligent local memory system for AI coding assistants.
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [3.2.0] - 2026-03-26
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **`slm doctor` command** — comprehensive pre-flight check: Python version, all dependency groups, embedding worker functional test, Ollama connectivity, API key validation, disk space, database integrity. Supports `--json` for agent-native output.
|
|
23
|
+
- **`slm hooks install`** listed in CLI reference and README.
|
|
24
|
+
- Dashboard, learning (lightgbm), and performance (diskcache, orjson) dependencies now install automatically during `npm install`.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Warmup reliability** — increased subprocess timeout from 60s to 180s for first-time model download. Added step-by-step progress output and direct in-process import diagnostics when worker fails.
|
|
28
|
+
- **Mode B default model** — changed from `phi3:mini` to `llama3.2` to match `provider_presets()` and reduce first-time setup friction.
|
|
29
|
+
- **postinstall.js** — now installs all 5 dependency groups (core, search, dashboard, learning, performance) with clear status messages per group.
|
|
30
|
+
- **Error messages** — all embedding worker failures, engine fallbacks, and dashboard errors now suggest `slm doctor` for diagnosis.
|
|
31
|
+
- **pyproject.toml** — added `diskcache` and `orjson` to core dependencies; aligned optional dependency versions with core.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
19
35
|
## [3.0.31] - 2026-03-21
|
|
20
36
|
|
|
21
37
|
### Fixed
|
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ Mathematical layers contribute **+12.7 percentage points** on average across 6 c
|
|
|
54
54
|
```bash
|
|
55
55
|
npm install -g superlocalmemory
|
|
56
56
|
slm setup # Choose mode (A/B/C)
|
|
57
|
+
slm doctor # Verify everything is working
|
|
57
58
|
slm warmup # Pre-download embedding model (~500MB, optional)
|
|
58
59
|
```
|
|
59
60
|
|
|
@@ -323,13 +324,15 @@ Three new tools for AI assistants:
|
|
|
323
324
|
| `slm trace "..."` | Recall with per-channel score breakdown |
|
|
324
325
|
| `slm status` | System status |
|
|
325
326
|
| `slm health` | Math layer health (Fisher, Sheaf, Langevin) |
|
|
327
|
+
| `slm doctor` | Pre-flight check (deps, worker, Ollama, database) |
|
|
326
328
|
| `slm mode a/b/c` | Switch operating mode |
|
|
327
329
|
| `slm setup` | Interactive first-time wizard |
|
|
328
330
|
| `slm warmup` | Pre-download embedding model |
|
|
329
331
|
| `slm migrate` | V2 to V3 migration |
|
|
330
|
-
| `slm dashboard` | Launch web dashboard |
|
|
332
|
+
| `slm dashboard` | Launch 17-tab web dashboard |
|
|
331
333
|
| `slm mcp` | Start MCP server (for IDE integration) |
|
|
332
334
|
| `slm connect` | Configure IDE integrations |
|
|
335
|
+
| `slm hooks install` | Wire auto-memory into Claude Code hooks |
|
|
333
336
|
| `slm profile list/create/switch` | Profile management |
|
|
334
337
|
|
|
335
338
|
---
|
|
@@ -367,13 +370,16 @@ Three new tools for AI assistants:
|
|
|
367
370
|
| **Node.js** | 14+ | npm package manager |
|
|
368
371
|
| **Python** | 3.11+ | V3 engine runtime |
|
|
369
372
|
|
|
370
|
-
All Python dependencies install automatically during `npm install
|
|
373
|
+
All Python dependencies install automatically during `npm install` — core math, dashboard server, learning engine, and performance optimizations. If anything fails, the installer shows exact fix commands. Run `slm doctor` after install to verify everything works. BM25 keyword search works even without embeddings — you're never fully blocked.
|
|
371
374
|
|
|
372
375
|
| Component | Size | When |
|
|
373
376
|
|:----------|:-----|:-----|
|
|
374
377
|
| Core libraries (numpy, scipy, networkx) | ~50MB | During install |
|
|
378
|
+
| Dashboard & MCP server (fastapi, uvicorn) | ~20MB | During install |
|
|
379
|
+
| Learning engine (lightgbm) | ~10MB | During install |
|
|
375
380
|
| Search engine (sentence-transformers, torch) | ~200MB | During install |
|
|
376
381
|
| Embedding model (nomic-embed-text-v1.5, 768d) | ~500MB | First use or `slm warmup` |
|
|
382
|
+
| **Mode B** requires [Ollama](https://ollama.com) + a model (`ollama pull llama3.2`) | ~2GB | Manual |
|
|
377
383
|
|
|
378
384
|
---
|
|
379
385
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "superlocalmemory"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.2.0"
|
|
4
4
|
description = "Information-geometric agent memory with mathematical guarantees"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
@@ -27,27 +27,29 @@ dependencies = [
|
|
|
27
27
|
"uvicorn>=0.42.0",
|
|
28
28
|
"websockets>=16.0",
|
|
29
29
|
"lightgbm>=4.0.0",
|
|
30
|
+
"diskcache>=5.6.0",
|
|
31
|
+
"orjson>=3.9.0",
|
|
30
32
|
]
|
|
31
33
|
|
|
32
34
|
[project.optional-dependencies]
|
|
33
35
|
search = [
|
|
34
36
|
"sentence-transformers>=2.5.0,<4.0.0",
|
|
35
|
-
"einops>=0.
|
|
37
|
+
"einops>=0.8.2",
|
|
36
38
|
"torch>=2.2.0",
|
|
37
39
|
"scikit-learn>=1.3.0,<2.0.0",
|
|
38
40
|
"geoopt>=0.5.0",
|
|
39
41
|
]
|
|
40
42
|
ui = [
|
|
41
|
-
"fastapi>=0.
|
|
42
|
-
"uvicorn
|
|
43
|
+
"fastapi[all]>=0.135.1",
|
|
44
|
+
"uvicorn>=0.42.0",
|
|
43
45
|
"python-multipart>=0.0.6,<1.0.0",
|
|
44
46
|
]
|
|
45
47
|
learning = [
|
|
46
|
-
"lightgbm>=4.0.0
|
|
48
|
+
"lightgbm>=4.0.0",
|
|
47
49
|
]
|
|
48
50
|
performance = [
|
|
49
|
-
"diskcache>=5.6.0
|
|
50
|
-
"orjson>=3.9.0
|
|
51
|
+
"diskcache>=5.6.0",
|
|
52
|
+
"orjson>=3.9.0",
|
|
51
53
|
]
|
|
52
54
|
full = [
|
|
53
55
|
"superlocalmemory[search,ui,learning,performance]",
|
package/scripts/postinstall.js
CHANGED
|
@@ -102,6 +102,7 @@ const coreDeps = [
|
|
|
102
102
|
'numpy>=1.26.0', 'scipy>=1.12.0', 'networkx>=3.0',
|
|
103
103
|
'httpx>=0.24.0', 'python-dateutil>=2.9.0',
|
|
104
104
|
'rank-bm25>=0.2.2', 'vaderSentiment>=3.3.2',
|
|
105
|
+
'einops>=0.8.2', 'mcp>=1.0.0',
|
|
105
106
|
];
|
|
106
107
|
|
|
107
108
|
if (pipInstall(coreDeps, 'core')) {
|
|
@@ -127,6 +128,35 @@ if (pipInstall(searchDeps, 'search')) {
|
|
|
127
128
|
console.log(' pip install sentence-transformers einops geoopt');
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
// Dashboard dependencies (IMPORTANT — enables web dashboard + MCP server)
|
|
132
|
+
const dashboardDeps = ['fastapi[all]>=0.135.1', 'uvicorn>=0.42.0', 'websockets>=16.0'];
|
|
133
|
+
console.log('\nInstalling dashboard & server dependencies...');
|
|
134
|
+
if (pipInstall(dashboardDeps, 'dashboard')) {
|
|
135
|
+
console.log('✓ Dashboard & MCP server dependencies installed (fastapi + uvicorn)');
|
|
136
|
+
} else {
|
|
137
|
+
console.log('⚠ Dashboard installation failed.');
|
|
138
|
+
console.log(' Run manually: pip install \'fastapi[all]\' uvicorn websockets');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Learning dependencies (enables adaptive retrieval after 200+ signals)
|
|
142
|
+
const learningDeps = ['lightgbm>=4.0.0'];
|
|
143
|
+
console.log('\nInstalling learning engine...');
|
|
144
|
+
if (pipInstall(learningDeps, 'learning')) {
|
|
145
|
+
console.log('✓ Learning engine installed (lightgbm — adaptive ranking)');
|
|
146
|
+
} else {
|
|
147
|
+
console.log('⚠ Learning installation failed (retrieval still works without it).');
|
|
148
|
+
console.log(' Run manually: pip install lightgbm');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Performance dependencies (optional — improves caching and JSON speed)
|
|
152
|
+
const perfDeps = ['diskcache>=5.6.0', 'orjson>=3.9.0'];
|
|
153
|
+
console.log('\nInstalling performance optimizations...');
|
|
154
|
+
if (pipInstall(perfDeps, 'performance')) {
|
|
155
|
+
console.log('✓ Performance optimizations installed (diskcache + orjson)');
|
|
156
|
+
} else {
|
|
157
|
+
console.log('⚠ Performance deps skipped (system works fine without them).');
|
|
158
|
+
}
|
|
159
|
+
|
|
130
160
|
// --- Step 4: Detect V2 installation ---
|
|
131
161
|
const V2_HOME = path.join(os.homedir(), '.claude-memory');
|
|
132
162
|
if (fs.existsSync(V2_HOME) && fs.existsSync(path.join(V2_HOME, 'memory.db'))) {
|
|
@@ -149,13 +179,17 @@ console.log(' ✓ SuperLocalMemory V3 installed successfully!');
|
|
|
149
179
|
console.log('');
|
|
150
180
|
console.log(' Quick start:');
|
|
151
181
|
console.log(' slm setup # First-time configuration');
|
|
152
|
-
console.log(' slm
|
|
182
|
+
console.log(' slm doctor # Pre-flight check (verify everything works)');
|
|
183
|
+
console.log(' slm warmup # Pre-download embedding model (~500MB)');
|
|
153
184
|
console.log(' slm remember "..." # Store a memory');
|
|
154
185
|
console.log(' slm recall "..." # Search memories');
|
|
186
|
+
console.log(' slm dashboard # Open 17-tab web dashboard');
|
|
155
187
|
console.log('');
|
|
156
188
|
console.log(' Prerequisites satisfied:');
|
|
157
189
|
console.log(' ✓ Python 3.11+');
|
|
158
190
|
console.log(' ✓ Core math & search libraries');
|
|
191
|
+
console.log(' ✓ Dashboard server (fastapi, uvicorn)');
|
|
192
|
+
console.log(' ✓ Learning engine (lightgbm)');
|
|
159
193
|
console.log(' ✓ Data directory (~/.superlocalmemory/)');
|
|
160
194
|
console.log('');
|
|
161
195
|
console.log(' Docs: https://github.com/qualixar/superlocalmemory/wiki');
|
|
@@ -32,6 +32,7 @@ def dispatch(args: Namespace) -> None:
|
|
|
32
32
|
"update": cmd_update,
|
|
33
33
|
"status": cmd_status,
|
|
34
34
|
"health": cmd_health,
|
|
35
|
+
"doctor": cmd_doctor,
|
|
35
36
|
"trace": cmd_trace,
|
|
36
37
|
"mcp": cmd_mcp,
|
|
37
38
|
"warmup": cmd_warmup,
|
|
@@ -592,6 +593,254 @@ def cmd_health(args: Namespace) -> None:
|
|
|
592
593
|
print(f" Mode: {config.mode.value.upper()}")
|
|
593
594
|
|
|
594
595
|
|
|
596
|
+
def cmd_doctor(args: Namespace) -> None:
|
|
597
|
+
"""Comprehensive pre-flight check — verify everything works."""
|
|
598
|
+
import shutil
|
|
599
|
+
from pathlib import Path
|
|
600
|
+
|
|
601
|
+
use_json = getattr(args, "json", False)
|
|
602
|
+
checks: list[dict] = []
|
|
603
|
+
passed = warned = failed = 0
|
|
604
|
+
|
|
605
|
+
def _check(name: str, status: str, detail: str, fix: str = ""):
|
|
606
|
+
nonlocal passed, warned, failed
|
|
607
|
+
checks.append({"name": name, "status": status, "detail": detail, "fix": fix})
|
|
608
|
+
if status == "PASS":
|
|
609
|
+
passed += 1
|
|
610
|
+
elif status == "WARN":
|
|
611
|
+
warned += 1
|
|
612
|
+
else:
|
|
613
|
+
failed += 1
|
|
614
|
+
if not use_json:
|
|
615
|
+
tag = {"PASS": "[PASS]", "WARN": "[WARN]", "FAIL": "[FAIL]"}[status]
|
|
616
|
+
line = f" {tag} {name}: {detail}"
|
|
617
|
+
if fix:
|
|
618
|
+
line += f"\n Fix: {fix}"
|
|
619
|
+
print(line)
|
|
620
|
+
|
|
621
|
+
if not use_json:
|
|
622
|
+
print("SuperLocalMemory V3 — Doctor (Pre-flight Check)")
|
|
623
|
+
print("=" * 50)
|
|
624
|
+
print()
|
|
625
|
+
|
|
626
|
+
# 1. Python version
|
|
627
|
+
v = sys.version_info
|
|
628
|
+
if v >= (3, 11):
|
|
629
|
+
_check("Python", "PASS", f"{v.major}.{v.minor}.{v.micro} (>= 3.11)")
|
|
630
|
+
else:
|
|
631
|
+
_check("Python", "FAIL", f"{v.major}.{v.minor}.{v.micro} (need >= 3.11)",
|
|
632
|
+
"Install Python 3.11+ from https://python.org/downloads/")
|
|
633
|
+
|
|
634
|
+
# 2. Core deps
|
|
635
|
+
core_modules = {
|
|
636
|
+
"numpy": "numpy", "scipy": "scipy", "networkx": "networkx",
|
|
637
|
+
"httpx": "httpx", "dateutil": "python-dateutil",
|
|
638
|
+
"rank_bm25": "rank-bm25", "vaderSentiment": "vadersentiment",
|
|
639
|
+
"einops": "einops",
|
|
640
|
+
}
|
|
641
|
+
core_ok, core_versions = [], []
|
|
642
|
+
for mod, pkg in core_modules.items():
|
|
643
|
+
try:
|
|
644
|
+
m = __import__(mod)
|
|
645
|
+
ver = getattr(m, "__version__", "?")
|
|
646
|
+
core_ok.append(mod)
|
|
647
|
+
core_versions.append(f"{mod} {ver}")
|
|
648
|
+
except ImportError:
|
|
649
|
+
pass
|
|
650
|
+
if len(core_ok) == len(core_modules):
|
|
651
|
+
_check("Core deps", "PASS", ", ".join(core_versions[:4]) + "...")
|
|
652
|
+
else:
|
|
653
|
+
missing = set(core_modules) - set(core_ok)
|
|
654
|
+
_check("Core deps", "FAIL", f"Missing: {', '.join(missing)}",
|
|
655
|
+
"pip install " + " ".join(core_modules[m] for m in missing))
|
|
656
|
+
|
|
657
|
+
# 3. Search deps
|
|
658
|
+
search_mods = {"sentence_transformers": "sentence-transformers", "torch": "torch",
|
|
659
|
+
"sklearn": "scikit-learn", "geoopt": "geoopt"}
|
|
660
|
+
search_ok = []
|
|
661
|
+
for mod, pkg in search_mods.items():
|
|
662
|
+
try:
|
|
663
|
+
__import__(mod)
|
|
664
|
+
search_ok.append(mod)
|
|
665
|
+
except ImportError:
|
|
666
|
+
pass
|
|
667
|
+
if len(search_ok) == len(search_mods):
|
|
668
|
+
_check("Search deps", "PASS", "sentence-transformers, torch, sklearn, geoopt")
|
|
669
|
+
else:
|
|
670
|
+
missing = set(search_mods) - set(search_ok)
|
|
671
|
+
_check("Search deps", "WARN", f"Missing: {', '.join(missing)}",
|
|
672
|
+
"pip install 'superlocalmemory[search]'")
|
|
673
|
+
|
|
674
|
+
# 4. Dashboard deps
|
|
675
|
+
dash_ok = True
|
|
676
|
+
for mod in ["fastapi", "uvicorn", "websockets"]:
|
|
677
|
+
try:
|
|
678
|
+
__import__(mod)
|
|
679
|
+
except ImportError:
|
|
680
|
+
dash_ok = False
|
|
681
|
+
break
|
|
682
|
+
if dash_ok:
|
|
683
|
+
_check("Dashboard deps", "PASS", "fastapi, uvicorn, websockets")
|
|
684
|
+
else:
|
|
685
|
+
_check("Dashboard deps", "WARN", "Missing dashboard deps",
|
|
686
|
+
"pip install 'fastapi[all]' uvicorn websockets")
|
|
687
|
+
|
|
688
|
+
# 5. Learning deps
|
|
689
|
+
try:
|
|
690
|
+
import lightgbm
|
|
691
|
+
_check("Learning deps", "PASS", f"lightgbm {lightgbm.__version__}")
|
|
692
|
+
except ImportError:
|
|
693
|
+
_check("Learning deps", "WARN", "lightgbm not installed",
|
|
694
|
+
"pip install lightgbm")
|
|
695
|
+
except OSError as exc:
|
|
696
|
+
_check("Learning deps", "WARN", f"lightgbm installed but broken: {exc}",
|
|
697
|
+
"brew install libomp && pip install --force-reinstall lightgbm")
|
|
698
|
+
|
|
699
|
+
# 6. Performance deps
|
|
700
|
+
perf_ok = []
|
|
701
|
+
for mod in ["diskcache", "orjson"]:
|
|
702
|
+
try:
|
|
703
|
+
__import__(mod)
|
|
704
|
+
perf_ok.append(mod)
|
|
705
|
+
except ImportError:
|
|
706
|
+
pass
|
|
707
|
+
if len(perf_ok) == 2:
|
|
708
|
+
_check("Performance deps", "PASS", "diskcache, orjson")
|
|
709
|
+
else:
|
|
710
|
+
missing = {"diskcache", "orjson"} - set(perf_ok)
|
|
711
|
+
_check("Performance deps", "WARN", f"Missing: {', '.join(missing)}",
|
|
712
|
+
"pip install diskcache orjson")
|
|
713
|
+
|
|
714
|
+
# 7. Embedding worker functional test
|
|
715
|
+
try:
|
|
716
|
+
import subprocess as _sp
|
|
717
|
+
import json as _json
|
|
718
|
+
|
|
719
|
+
env = {
|
|
720
|
+
**__import__("os").environ,
|
|
721
|
+
"CUDA_VISIBLE_DEVICES": "",
|
|
722
|
+
"PYTORCH_MPS_HIGH_WATERMARK_RATIO": "0.0",
|
|
723
|
+
"TOKENIZERS_PARALLELISM": "false",
|
|
724
|
+
"TORCH_DEVICE": "cpu",
|
|
725
|
+
}
|
|
726
|
+
proc = _sp.Popen(
|
|
727
|
+
[sys.executable, "-m", "superlocalmemory.core.embedding_worker"],
|
|
728
|
+
stdin=_sp.PIPE, stdout=_sp.PIPE, stderr=_sp.DEVNULL,
|
|
729
|
+
text=True, bufsize=1, env=env,
|
|
730
|
+
)
|
|
731
|
+
proc.stdin.write(_json.dumps({"cmd": "ping"}) + "\n")
|
|
732
|
+
proc.stdin.flush()
|
|
733
|
+
|
|
734
|
+
import select as _sel
|
|
735
|
+
ready, _, _ = _sel.select([proc.stdout], [], [], 30)
|
|
736
|
+
if ready:
|
|
737
|
+
resp = _json.loads(proc.stdout.readline())
|
|
738
|
+
if resp.get("ok"):
|
|
739
|
+
_check("Embedding worker", "PASS",
|
|
740
|
+
f"responsive (PID {proc.pid}, Python {sys.executable})")
|
|
741
|
+
else:
|
|
742
|
+
_check("Embedding worker", "FAIL",
|
|
743
|
+
f"error: {resp.get('error', 'unknown')}",
|
|
744
|
+
"pip install sentence-transformers einops torch")
|
|
745
|
+
else:
|
|
746
|
+
_check("Embedding worker", "FAIL", "timed out (30s)",
|
|
747
|
+
"slm warmup")
|
|
748
|
+
proc.stdin.write(_json.dumps({"cmd": "quit"}) + "\n")
|
|
749
|
+
proc.stdin.flush()
|
|
750
|
+
proc.wait(timeout=5)
|
|
751
|
+
except FileNotFoundError:
|
|
752
|
+
_check("Embedding worker", "FAIL", "embedding_worker module not found",
|
|
753
|
+
"Reinstall: npm install -g superlocalmemory")
|
|
754
|
+
except Exception as exc:
|
|
755
|
+
_check("Embedding worker", "FAIL", str(exc),
|
|
756
|
+
"slm warmup")
|
|
757
|
+
|
|
758
|
+
# 8. Ollama connectivity (Mode B only)
|
|
759
|
+
try:
|
|
760
|
+
from superlocalmemory.core.config import SLMConfig
|
|
761
|
+
config = SLMConfig.load()
|
|
762
|
+
if config.mode.value == "b":
|
|
763
|
+
import httpx
|
|
764
|
+
try:
|
|
765
|
+
resp = httpx.get(
|
|
766
|
+
f"{config.llm.api_base}/api/tags", timeout=5.0,
|
|
767
|
+
)
|
|
768
|
+
if resp.status_code == 200:
|
|
769
|
+
models = [m["name"].split(":")[0] for m in resp.json().get("models", [])]
|
|
770
|
+
has_llm = config.llm.model.split(":")[0] in models
|
|
771
|
+
if has_llm:
|
|
772
|
+
_check("Ollama", "PASS",
|
|
773
|
+
f"running, {len(models)} models, '{config.llm.model}' available")
|
|
774
|
+
else:
|
|
775
|
+
_check("Ollama", "WARN",
|
|
776
|
+
f"running but '{config.llm.model}' not pulled",
|
|
777
|
+
f"ollama pull {config.llm.model}")
|
|
778
|
+
else:
|
|
779
|
+
_check("Ollama", "WARN", f"HTTP {resp.status_code}",
|
|
780
|
+
"brew services start ollama")
|
|
781
|
+
except Exception:
|
|
782
|
+
_check("Ollama", "WARN", "not reachable at " + config.llm.api_base,
|
|
783
|
+
"brew services start ollama")
|
|
784
|
+
elif config.mode.value == "c":
|
|
785
|
+
# Mode C — check API key
|
|
786
|
+
if config.llm.api_key:
|
|
787
|
+
_check("API key", "PASS",
|
|
788
|
+
f"provider={config.llm.provider}, key=***{config.llm.api_key[-4:]}")
|
|
789
|
+
else:
|
|
790
|
+
_check("API key", "WARN", "no API key configured",
|
|
791
|
+
"slm provider set")
|
|
792
|
+
except Exception:
|
|
793
|
+
pass # Config load failed — already caught above
|
|
794
|
+
|
|
795
|
+
# 9. Disk space
|
|
796
|
+
slm_home = Path.home() / ".superlocalmemory"
|
|
797
|
+
try:
|
|
798
|
+
usage = shutil.disk_usage(slm_home if slm_home.exists() else Path.home())
|
|
799
|
+
free_gb = usage.free / (1024 ** 3)
|
|
800
|
+
if free_gb >= 2.0:
|
|
801
|
+
_check("Disk space", "PASS", f"{free_gb:.1f} GB free")
|
|
802
|
+
else:
|
|
803
|
+
_check("Disk space", "WARN", f"{free_gb:.1f} GB free (< 2 GB)",
|
|
804
|
+
"Free up disk space")
|
|
805
|
+
except Exception:
|
|
806
|
+
pass
|
|
807
|
+
|
|
808
|
+
# 10. Database integrity
|
|
809
|
+
db_path = slm_home / "memory.db"
|
|
810
|
+
if db_path.exists():
|
|
811
|
+
try:
|
|
812
|
+
import sqlite3
|
|
813
|
+
conn = sqlite3.connect(str(db_path))
|
|
814
|
+
result = conn.execute("PRAGMA integrity_check").fetchone()
|
|
815
|
+
conn.close()
|
|
816
|
+
if result and result[0] == "ok":
|
|
817
|
+
size_mb = db_path.stat().st_size / (1024 * 1024)
|
|
818
|
+
_check("Database", "PASS", f"OK ({size_mb:.2f} MB)")
|
|
819
|
+
else:
|
|
820
|
+
_check("Database", "FAIL", f"integrity check: {result}",
|
|
821
|
+
"Backup and recreate database")
|
|
822
|
+
except Exception as exc:
|
|
823
|
+
_check("Database", "FAIL", str(exc))
|
|
824
|
+
else:
|
|
825
|
+
_check("Database", "PASS", "not yet created (will initialize on first use)")
|
|
826
|
+
|
|
827
|
+
# Summary
|
|
828
|
+
if use_json:
|
|
829
|
+
from superlocalmemory.cli.json_output import json_print
|
|
830
|
+
next_actions = []
|
|
831
|
+
for c in checks:
|
|
832
|
+
if c["fix"]:
|
|
833
|
+
next_actions.append({"command": c["fix"], "description": f"Fix {c['name']}"})
|
|
834
|
+
json_print("doctor", data={
|
|
835
|
+
"checks": checks,
|
|
836
|
+
"summary": {"passed": passed, "warned": warned, "failed": failed},
|
|
837
|
+
}, next_actions=next_actions)
|
|
838
|
+
else:
|
|
839
|
+
print(f"\nSummary: {passed} passed, {warned} warnings, {failed} failed")
|
|
840
|
+
if failed > 0:
|
|
841
|
+
print("Run the suggested fix commands above, then re-run: slm doctor")
|
|
842
|
+
|
|
843
|
+
|
|
595
844
|
def cmd_trace(args: Namespace) -> None:
|
|
596
845
|
"""Recall with per-channel score breakdown."""
|
|
597
846
|
from superlocalmemory.core.engine import MemoryEngine
|
|
@@ -654,35 +903,74 @@ def cmd_mcp(_args: Namespace) -> None:
|
|
|
654
903
|
|
|
655
904
|
def cmd_warmup(_args: Namespace) -> None:
|
|
656
905
|
"""Pre-download the embedding model so first use is instant."""
|
|
657
|
-
|
|
658
|
-
|
|
906
|
+
import superlocalmemory.core.embeddings as _emb_mod
|
|
907
|
+
|
|
908
|
+
print("SuperLocalMemory V3 — Embedding Model Warmup")
|
|
909
|
+
print("=" * 50)
|
|
910
|
+
print(f" Python: {sys.executable}")
|
|
911
|
+
print(f" Model: nomic-ai/nomic-embed-text-v1.5 (~500MB)")
|
|
912
|
+
print()
|
|
913
|
+
|
|
914
|
+
# Increase timeout for first-time download
|
|
915
|
+
original_timeout = _emb_mod._SUBPROCESS_RESPONSE_TIMEOUT
|
|
916
|
+
_emb_mod._SUBPROCESS_RESPONSE_TIMEOUT = 180 # 3 min for cold start
|
|
659
917
|
|
|
660
918
|
try:
|
|
661
919
|
from superlocalmemory.core.config import EmbeddingConfig
|
|
662
920
|
from superlocalmemory.core.embeddings import EmbeddingService
|
|
663
921
|
|
|
664
922
|
config = EmbeddingConfig()
|
|
923
|
+
|
|
924
|
+
print("Step 1/3: Spawning embedding worker subprocess...")
|
|
665
925
|
svc = EmbeddingService(config)
|
|
666
926
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
927
|
+
if not svc.is_available:
|
|
928
|
+
print("\n[FAIL] Embedding service not available.")
|
|
929
|
+
_warmup_diagnose()
|
|
930
|
+
return
|
|
931
|
+
|
|
932
|
+
print("Step 2/3: Loading model (may download ~500MB on first run)...")
|
|
933
|
+
emb = svc.embed("warmup test")
|
|
934
|
+
|
|
935
|
+
if emb and len(emb) == config.dimension:
|
|
936
|
+
print("Step 3/3: Verifying embedding output...")
|
|
937
|
+
print(f"\n[PASS] Model ready: {config.model_name} ({config.dimension}-dim)")
|
|
938
|
+
print("Semantic search is fully operational.")
|
|
677
939
|
else:
|
|
678
|
-
print("\
|
|
679
|
-
|
|
940
|
+
print("\n[FAIL] Model loaded but embedding verification failed.")
|
|
941
|
+
_warmup_diagnose()
|
|
942
|
+
|
|
680
943
|
except ImportError as exc:
|
|
681
|
-
print(f"\
|
|
682
|
-
print("
|
|
944
|
+
print(f"\n[FAIL] Missing dependency: {exc}")
|
|
945
|
+
print("Fix: pip install sentence-transformers einops torch")
|
|
683
946
|
except Exception as exc:
|
|
684
|
-
print(f"\
|
|
685
|
-
|
|
947
|
+
print(f"\n[FAIL] Warmup failed: {exc}")
|
|
948
|
+
_warmup_diagnose()
|
|
949
|
+
finally:
|
|
950
|
+
_emb_mod._SUBPROCESS_RESPONSE_TIMEOUT = original_timeout
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
def _warmup_diagnose() -> None:
|
|
954
|
+
"""Diagnostic helper when warmup fails."""
|
|
955
|
+
print("\nDiagnosing...")
|
|
956
|
+
print(f" Python executable: {sys.executable}")
|
|
957
|
+
try:
|
|
958
|
+
from sentence_transformers import SentenceTransformer
|
|
959
|
+
print(" sentence-transformers: importable")
|
|
960
|
+
m = SentenceTransformer(
|
|
961
|
+
"nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True, device="cpu",
|
|
962
|
+
)
|
|
963
|
+
v = m.encode(["test"], normalize_embeddings=True)
|
|
964
|
+
print(f" Direct embed: OK (dim={v.shape[1]})")
|
|
965
|
+
print("\n Issue: Subprocess worker failed but direct import works.")
|
|
966
|
+
print(" This is likely a Python path mismatch between Node.js wrapper")
|
|
967
|
+
print(" and your current shell. Run: slm doctor")
|
|
968
|
+
except ImportError as ie:
|
|
969
|
+
print(f" sentence-transformers: NOT importable ({ie})")
|
|
970
|
+
print(" Fix: pip install sentence-transformers einops torch")
|
|
971
|
+
except Exception as de:
|
|
972
|
+
print(f" Direct embed failed: {de}")
|
|
973
|
+
print(" Run: slm doctor")
|
|
686
974
|
|
|
687
975
|
|
|
688
976
|
def cmd_dashboard(args: Namespace) -> None:
|
|
@@ -690,7 +978,8 @@ def cmd_dashboard(args: Namespace) -> None:
|
|
|
690
978
|
try:
|
|
691
979
|
import uvicorn
|
|
692
980
|
except ImportError:
|
|
693
|
-
print("Dashboard requires
|
|
981
|
+
print("Dashboard requires additional deps. Run: slm doctor")
|
|
982
|
+
print("Or install manually: pip install 'fastapi[all]' uvicorn")
|
|
694
983
|
sys.exit(1)
|
|
695
984
|
|
|
696
985
|
import socket
|
|
@@ -153,6 +153,10 @@ def main() -> None:
|
|
|
153
153
|
trace_p.add_argument("query", help="Search query")
|
|
154
154
|
trace_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
155
155
|
|
|
156
|
+
# -- Diagnostics (continued) ----------------------------------------
|
|
157
|
+
doctor_p = sub.add_parser("doctor", help="Pre-flight check: deps, embedding worker, connectivity")
|
|
158
|
+
doctor_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
159
|
+
|
|
156
160
|
# -- Services ------------------------------------------------------
|
|
157
161
|
sub.add_parser("mcp", help="Start MCP server (stdio transport for IDE integration)")
|
|
158
162
|
sub.add_parser("warmup", help="Pre-download embedding model (~500MB, one-time)")
|
|
@@ -164,7 +164,12 @@ class EmbeddingService:
|
|
|
164
164
|
_SUBPROCESS_RESPONSE_TIMEOUT,
|
|
165
165
|
)
|
|
166
166
|
if not resp_line:
|
|
167
|
-
logger.warning(
|
|
167
|
+
logger.warning(
|
|
168
|
+
"Embedding worker timed out after %ds. On first run, model "
|
|
169
|
+
"download can take several minutes. Run 'slm doctor' to "
|
|
170
|
+
"diagnose or 'slm warmup' to pre-download the model.",
|
|
171
|
+
_SUBPROCESS_RESPONSE_TIMEOUT,
|
|
172
|
+
)
|
|
168
173
|
self._kill_worker()
|
|
169
174
|
return None
|
|
170
175
|
resp = json.loads(resp_line)
|
|
@@ -174,7 +179,11 @@ class EmbeddingService:
|
|
|
174
179
|
self._reset_idle_timer()
|
|
175
180
|
return resp["vectors"]
|
|
176
181
|
except (BrokenPipeError, OSError, json.JSONDecodeError) as exc:
|
|
177
|
-
logger.warning(
|
|
182
|
+
logger.warning(
|
|
183
|
+
"Embedding worker communication failed: %s. "
|
|
184
|
+
"Run 'slm doctor' to check dependencies and Python version.",
|
|
185
|
+
exc,
|
|
186
|
+
)
|
|
178
187
|
self._kill_worker()
|
|
179
188
|
return None
|
|
180
189
|
|
|
@@ -231,7 +240,12 @@ class EmbeddingService:
|
|
|
231
240
|
logger.info("Embedding worker spawned (PID %d)", self._worker_proc.pid)
|
|
232
241
|
self._worker_ready = True
|
|
233
242
|
except Exception as exc:
|
|
234
|
-
logger.warning(
|
|
243
|
+
logger.warning(
|
|
244
|
+
"Failed to spawn embedding worker: %s. "
|
|
245
|
+
"Run 'slm doctor' to verify your Python environment. "
|
|
246
|
+
"Using Python: %s",
|
|
247
|
+
exc, sys.executable,
|
|
248
|
+
)
|
|
235
249
|
self._available = False
|
|
236
250
|
self._worker_proc = None
|
|
237
251
|
|
|
@@ -175,9 +175,9 @@ class MemoryEngine:
|
|
|
175
175
|
emb = cls(emb_cfg)
|
|
176
176
|
if emb.is_available:
|
|
177
177
|
return emb
|
|
178
|
-
logger.warning("EmbeddingService not available. BM25-only mode.")
|
|
178
|
+
logger.warning("EmbeddingService not available. BM25-only mode. Run 'slm doctor' to diagnose.")
|
|
179
179
|
except Exception as exc:
|
|
180
|
-
logger.warning("Embeddings unavailable (%s). BM25-only mode.", exc)
|
|
180
|
+
logger.warning("Embeddings unavailable (%s). BM25-only mode. Run 'slm doctor' to diagnose.", exc)
|
|
181
181
|
return None
|
|
182
182
|
|
|
183
183
|
def store(
|
|
@@ -169,7 +169,7 @@ class WorkerPool:
|
|
|
169
169
|
|
|
170
170
|
resp_line = self._proc.stdout.readline()
|
|
171
171
|
if not resp_line:
|
|
172
|
-
logger.warning("Worker returned empty, restarting")
|
|
172
|
+
logger.warning("Worker returned empty, restarting. Run 'slm doctor' to diagnose.")
|
|
173
173
|
self._kill()
|
|
174
174
|
return {"ok": False, "error": "Worker died"}
|
|
175
175
|
|
|
@@ -177,7 +177,7 @@ class WorkerPool:
|
|
|
177
177
|
return json.loads(resp_line)
|
|
178
178
|
|
|
179
179
|
except (BrokenPipeError, OSError, json.JSONDecodeError) as exc:
|
|
180
|
-
logger.warning("Worker communication failed: %s", exc)
|
|
180
|
+
logger.warning("Worker communication failed: %s. Run 'slm doctor' to diagnose.", exc)
|
|
181
181
|
self._kill()
|
|
182
182
|
return {"ok": False, "error": str(exc)}
|
|
183
183
|
|
|
@@ -207,7 +207,7 @@ class WorkerPool:
|
|
|
207
207
|
)
|
|
208
208
|
logger.info("Recall worker spawned (PID %d)", self._proc.pid)
|
|
209
209
|
except Exception as exc:
|
|
210
|
-
logger.error("Failed to spawn recall worker: %s", exc)
|
|
210
|
+
logger.error("Failed to spawn recall worker: %s. Run 'slm doctor' to diagnose. Python: %s", exc, sys.executable)
|
|
211
211
|
self._proc = None
|
|
212
212
|
|
|
213
213
|
def _kill(self) -> None:
|